UNPKG

peertube-plugin-transposer-connector

Version:

Transposer connector is a PeerTube language tool plugin to transcribe and translate with Whisper

656 lines (591 loc) 26.3 kB
const initLanguageMetadata = (router, languageMetadataDAO, peertubeHelpers) => { // Helper function to get video details and extract numeric ID const getVideoNumericId = async (idParam) => { // If the ID is already numeric, return it directly if (!isNaN(idParam) && Number.isInteger(Number(idParam))) { peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: ID ${idParam} is already numeric, using directly` ); return Number(idParam); } try { peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: Converting UUID/slug ${idParam} to numeric ID via PeerTube API` ); // Make a request to the videos API to get the full video object const webserverUrl = peertubeHelpers.config.getWebserverUrl(); const response = await fetch(`${webserverUrl}/api/v1/videos/${idParam}`); if (!response.ok) { peertubeHelpers.logger.error( `[transposer-connector] LanguageMetadataController: PeerTube API returned status ${response.status} for video ${idParam}` ); throw new Error(`Video API returned status: ${response.status}`); } const data = await response.json(); // Extract the numeric ID from the response if (data && data.id) { peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: Successfully converted ${idParam} to numeric ID ${data.id}` ); return data.id; } throw new Error("Video ID not found in response"); } catch (error) { peertubeHelpers.logger.error( `[transposer-connector] LanguageMetadataController: Failed to resolve video ID for ${idParam}: ${error.message}` ); throw new Error("Failed to resolve video ID"); } }; // Retrieve Specific Languages / All Metadata: router.get("/language/metadata/video/:id", async (req, res) => { try { const idParam = req.params.id; peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: Starting metadata retrieval request for video ${idParam}` ); // Get the numeric ID let numericVideoId; try { numericVideoId = await getVideoNumericId(idParam); } catch (error) { peertubeHelpers.logger.error( `[transposer-connector] LanguageMetadataController: Video not found for ID ${idParam}` ); return res.status(404).json({ error: "Video not found" }); } const languages = req.query.languages ? req.query.languages.split(",") : []; const attributes = req.query.attribute ? req.query.attribute.split(",") : []; peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: Fetching metadata for video ${numericVideoId}, languages: [${ languages.join(", ") || "all" }], attributes: [${attributes.join(", ") || "all"}]` ); const completeMetadata = await languageMetadataDAO.findLanguageMetadataByIdAndLanguages( numericVideoId, languages ); if (!completeMetadata) { peertubeHelpers.logger.warn( `[transposer-connector] LanguageMetadataController: No metadata found for video ${numericVideoId}` ); return res .status(404) .json({ error: "Not Found: Resource not available" }); } const transformedObject = {}; completeMetadata.forEach((item) => { if (languages.length === 0 || languages.includes(item.language)) { if (attributes.length === 0 || attributes.includes(item.attribute)) { if (!transformedObject[item.attribute]) { transformedObject[item.attribute] = {}; } transformedObject[item.attribute][item.language] = item.value; } } }); peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: Successfully retrieved metadata for video ${numericVideoId}, returned ${ Object.keys(transformedObject).length } attributes` ); return res.json(transformedObject); } catch (error) { peertubeHelpers.logger.error( `[transposer-connector] LanguageMetadataController: Exception during metadata retrieval for video ${req.params.id}: ${error.message}` ); return res.status(500).json({ error: "Internal server error" }); } }); // Retrieve All Languages / Specific Metadata: router.get("/language/metadata/video/:id/:attribute", async (req, res) => { try { const idParam = req.params.id; const attribute = req.params.attribute; peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: Starting specific attribute retrieval for video ${idParam}, attribute: ${attribute}` ); // Get the numeric ID let numericVideoId; try { numericVideoId = await getVideoNumericId(idParam); } catch (error) { peertubeHelpers.logger.error( `[transposer-connector] LanguageMetadataController: Video not found for ID ${idParam}` ); return res.status(404).json({ error: "Video not found" }); } const specificMetadata = await languageMetadataDAO.findSpecificLanguageMetadata( numericVideoId, attribute ); if (!specificMetadata) { peertubeHelpers.logger.warn( `[transposer-connector] LanguageMetadataController: No metadata found for video ${numericVideoId}, attribute ${attribute}` ); return res .status(404) .json({ error: "Not Found: Resource not available" }); } const transformedObject = {}; specificMetadata.forEach((item) => { transformedObject[item.language] = item.value; }); peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: Successfully retrieved specific metadata for video ${numericVideoId}, attribute ${attribute}, returned ${ Object.keys(transformedObject).length } languages` ); return res.json(transformedObject); } catch (error) { peertubeHelpers.logger.error( `[transposer-connector] LanguageMetadataController: Exception during specific metadata retrieval for video ${req.params.id}, attribute ${req.params.attribute}: ${error.message}` ); return res.status(500).json({ error: "Internal server error" }); } }); router.get("/video/:id/complete", async (req, res) => { try { const idParam = req.params.id; peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: Starting complete video data retrieval for video ${idParam}` ); // Get the numeric ID let numericVideoId; try { numericVideoId = await getVideoNumericId(idParam); } catch (error) { peertubeHelpers.logger.error( `[transposer-connector] LanguageMetadataController: Video not found for ID ${idParam}` ); return res.status(404).json({ error: "Video not found" }); } // Get query parameters for filtering const languages = req.query.languages ? req.query.languages.split(",") : []; const attributes = req.query.attribute ? req.query.attribute.split(",") : []; peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: Fetching complete data for video ${numericVideoId}, languages filter: [${ languages.join(", ") || "all" }], attributes filter: [${attributes.join(", ") || "all"}]` ); // Fetch metadata let metadata = {}; try { const completeMetadata = await languageMetadataDAO.findLanguageMetadataByIdAndLanguages( numericVideoId, languages ); if (completeMetadata && completeMetadata.length > 0) { const transformedMetadata = {}; completeMetadata.forEach((item) => { if (languages.length === 0 || languages.includes(item.language)) { if ( attributes.length === 0 || attributes.includes(item.attribute) ) { if (!transformedMetadata[item.attribute]) { transformedMetadata[item.attribute] = {}; } transformedMetadata[item.attribute][item.language] = item.value; } } }); metadata = transformedMetadata; peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: Retrieved ${ Object.keys(metadata).length } metadata attributes for video ${numericVideoId}` ); } else { peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: No metadata found for video ${numericVideoId}` ); } } catch (error) { peertubeHelpers.logger.error( `[transposer-connector] LanguageMetadataController: Error fetching metadata for video ${numericVideoId}: ${error.message}` ); metadata = {}; } // Fetch captions let captions = []; try { peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: Fetching captions for video ${numericVideoId}` ); const webserverUrl = peertubeHelpers.config.getWebserverUrl(); const captionsResponse = await fetch( `${webserverUrl}/api/v1/videos/${numericVideoId}/captions` ); if (captionsResponse.ok) { const captionsData = await captionsResponse.json(); captions = captionsData.data || []; // Filter captions by language if specified if (languages.length > 0) { const originalCount = captions.length; captions = captions.filter((caption) => languages.includes(caption.language.id) ); peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: Filtered captions for video ${numericVideoId}, ${originalCount} -> ${captions.length} captions after language filter` ); } else { peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: Retrieved ${captions.length} captions for video ${numericVideoId}` ); } } else { peertubeHelpers.logger.warn( `[transposer-connector] LanguageMetadataController: Failed to fetch captions for video ${numericVideoId}, status: ${captionsResponse.status}` ); } } catch (error) { peertubeHelpers.logger.error( `[transposer-connector] LanguageMetadataController: Exception while fetching captions for video ${numericVideoId}: ${error.message}` ); captions = []; } // Combine the data const combinedData = { videoId: numericVideoId, metadata: metadata, captions: { total: captions.length, data: captions, }, availableLanguages: { metadata: metadata ? Object.keys(metadata).reduce((acc, attr) => { Object.keys(metadata[attr]).forEach((lang) => { if (!acc.includes(lang)) acc.push(lang); }); return acc; }, []) : [], captions: captions.map((caption) => caption.language.id), }, }; peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: Successfully compiled complete data for video ${numericVideoId}, metadata attributes: ${ Object.keys(metadata).length }, captions: ${captions.length}` ); return res.json(combinedData); } catch (error) { peertubeHelpers.logger.error( `[transposer-connector] LanguageMetadataController: Exception during complete video data retrieval for video ${req.params.id}: ${error.message}` ); return res.status(500).json({ error: "Internal server error" }); } }); // NEW: Get combined video data with caption content router.get("/video/:id/complete-with-content", async (req, res) => { try { const idParam = req.params.id; peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: Starting complete video data with content retrieval for video ${idParam}` ); // Get the numeric ID let numericVideoId; try { numericVideoId = await getVideoNumericId(idParam); } catch (error) { peertubeHelpers.logger.error( `[transposer-connector] LanguageMetadataController: Video not found for ID ${idParam}` ); return res.status(404).json({ error: "Video not found" }); } // Get query parameters for filtering const languages = req.query.languages ? req.query.languages.split(",") : []; const attributes = req.query.attribute ? req.query.attribute.split(",") : []; peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: Fetching complete data with content for video ${numericVideoId}, languages filter: [${ languages.join(", ") || "all" }], attributes filter: [${attributes.join(", ") || "all"}]` ); // Fetch metadata (same as above) let metadata = {}; try { const completeMetadata = await languageMetadataDAO.findLanguageMetadataByIdAndLanguages( numericVideoId, languages ); if (completeMetadata && completeMetadata.length > 0) { const transformedMetadata = {}; completeMetadata.forEach((item) => { if (languages.length === 0 || languages.includes(item.language)) { if ( attributes.length === 0 || attributes.includes(item.attribute) ) { if (!transformedMetadata[item.attribute]) { transformedMetadata[item.attribute] = {}; } transformedMetadata[item.attribute][item.language] = item.value; } } }); metadata = transformedMetadata; peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: Retrieved ${ Object.keys(metadata).length } metadata attributes for video ${numericVideoId}` ); } } catch (error) { peertubeHelpers.logger.error( `[transposer-connector] LanguageMetadataController: Error fetching metadata for video ${numericVideoId}: ${error.message}` ); metadata = {}; } // Fetch captions with content let captions = []; try { peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: Fetching captions with content for video ${numericVideoId}` ); const webserverUrl = peertubeHelpers.config.getWebserverUrl(); const captionsResponse = await fetch( `${webserverUrl}/api/v1/videos/${numericVideoId}/captions` ); if (captionsResponse.ok) { const captionsData = await captionsResponse.json(); let captionsList = captionsData.data || []; // Filter captions by language if specified if (languages.length > 0) { const originalCount = captionsList.length; captionsList = captionsList.filter((caption) => languages.includes(caption.language.id) ); peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: Filtered captions for video ${numericVideoId}, ${originalCount} -> ${captionsList.length} captions after language filter` ); } // Fetch content for each caption peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: Fetching content for ${captionsList.length} caption files for video ${numericVideoId}` ); for (const caption of captionsList) { try { const captionContentResponse = await fetch( `${webserverUrl}${caption.captionPath}` ); if (captionContentResponse.ok) { caption.content = await captionContentResponse.text(); peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: Successfully fetched content for caption ${caption.language.id} for video ${numericVideoId}` ); } else { caption.content = null; caption.contentError = `Failed to fetch content: ${captionContentResponse.status}`; peertubeHelpers.logger.warn( `[transposer-connector] LanguageMetadataController: Failed to fetch caption content for ${caption.language.id} for video ${numericVideoId}, status: ${captionContentResponse.status}` ); } } catch (error) { caption.content = null; caption.contentError = `Error fetching content: ${error.message}`; peertubeHelpers.logger.error( `[transposer-connector] LanguageMetadataController: Exception fetching caption content for ${caption.language.id} for video ${numericVideoId}: ${error.message}` ); } } captions = captionsList; } else { peertubeHelpers.logger.warn( `[transposer-connector] LanguageMetadataController: Failed to fetch captions for video ${numericVideoId}, status: ${captionsResponse.status}` ); } } catch (error) { peertubeHelpers.logger.error( `[transposer-connector] LanguageMetadataController: Exception while fetching captions with content for video ${numericVideoId}: ${error.message}` ); captions = []; } // Combine the data const combinedData = { videoId: numericVideoId, metadata: metadata, captions: { total: captions.length, data: captions, }, availableLanguages: { metadata: metadata ? Object.keys(metadata).reduce((acc, attr) => { Object.keys(metadata[attr]).forEach((lang) => { if (!acc.includes(lang)) acc.push(lang); }); return acc; }, []) : [], captions: captions.map((caption) => caption.language.id), }, }; peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: Successfully compiled complete data with content for video ${numericVideoId}, metadata attributes: ${ Object.keys(metadata).length }, captions with content: ${captions.length}` ); return res.json(combinedData); } catch (error) { peertubeHelpers.logger.error( `[transposer-connector] LanguageMetadataController: Exception during complete video data with content retrieval for video ${req.params.id}: ${error.message}` ); return res.status(500).json({ error: "Internal server error" }); } }); // Send all language metadata at once to the db router.post("/language/metadata/video/:id/metadata", async (req, res) => { try { const idParam = req.params.id; peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: Starting metadata POST request for video ${idParam}` ); let authUser; try { peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: Attempting to authenticate user for video ${idParam} metadata update` ); authUser = await peertubeHelpers.user.getAuthUser(res); if (!authUser) { peertubeHelpers.logger.warn( `[transposer-connector] LanguageMetadataController: Authentication failed for video ${idParam} metadata update - no user found` ); return res.status(401).json({ error: "Authentication required" }); } peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: User authenticated for video ${idParam}: ${authUser.username} (ID: ${authUser.id}, role: ${authUser.role})` ); } catch (error) { peertubeHelpers.logger.error( `[transposer-connector] LanguageMetadataController: Authentication error for video ${idParam}: ${error.message}` ); return res.status(401).json({ error: "Authentication failed" }); } const metadata = req.body; peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: Validating metadata payload for video ${idParam}` ); if (!metadata || typeof metadata !== "object") { peertubeHelpers.logger.warn( `[transposer-connector] LanguageMetadataController: Invalid metadata format provided for video ${idParam}` ); return res.status(400).json({ error: "Invalid metadata format" }); } const languageCount = Object.keys(metadata).length; peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: Metadata validation passed for video ${idParam}, processing ${languageCount} languages` ); let numericVideoId; try { numericVideoId = await getVideoNumericId(idParam); } catch (error) { peertubeHelpers.logger.error( `[transposer-connector] LanguageMetadataController: Video not found for ID ${idParam} during metadata update` ); return res.status(404).json({ error: "Video not found" }); } let video; try { peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: Loading video details for ownership check, video ${numericVideoId}` ); video = await peertubeHelpers.videos.loadByIdOrUUID(numericVideoId); if (!video) { peertubeHelpers.logger.warn( `[transposer-connector] LanguageMetadataController: Video ${numericVideoId} not found during ownership verification` ); return res.status(404).json({ error: "Video not found" }); } peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: Video loaded for ownership check: "${video.name}" (channel: ${video.VideoChannel?.name})` ); } catch (error) { peertubeHelpers.logger.error( `[transposer-connector] LanguageMetadataController: Failed to load video ${numericVideoId} for ownership check: ${error.message}` ); return res.status(500).json({ error: "Failed to load video details" }); } peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: Checking permissions for video ${numericVideoId} - user account: ${authUser.Account?.id}, video channel account: ${video.VideoChannel?.Account?.id}` ); const isVideoOwner = video.VideoChannel?.Account?.id === authUser.Account?.id; const isAdmin = authUser.role === 0; if (!isVideoOwner && !isAdmin) { peertubeHelpers.logger.warn( `[transposer-connector] LanguageMetadataController: Permission denied for user ${authUser.username} to modify video ${numericVideoId} metadata (not owner or admin)` ); return res.status(403).json({ error: "You don't have permission to modify this video's metadata", }); } peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: Permission granted for video ${numericVideoId} metadata update (user: ${authUser.username}, isOwner: ${isVideoOwner}, isAdmin: ${isAdmin})` ); // Process and validate metadata let totalAttributes = 0; Object.keys(metadata).forEach((langCode) => { if (typeof metadata[langCode] !== "object") { peertubeHelpers.logger.error( `[transposer-connector] LanguageMetadataController: Invalid format for language '${langCode}' in video ${numericVideoId} metadata` ); return res.status(400).json({ error: `Invalid format for language '${langCode}'`, }); } const attributeCount = Object.keys(metadata[langCode]).length; totalAttributes += attributeCount; peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: Processing ${attributeCount} attributes for language '${langCode}' in video ${numericVideoId}` ); Object.keys(metadata[langCode]).forEach((field) => { if ( metadata[langCode][field] === null || metadata[langCode][field] === undefined ) { metadata[langCode][field] = ""; } metadata[langCode][field] = String(metadata[langCode][field]).trim(); }); }); peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: Saving ${totalAttributes} total attributes across ${languageCount} languages to database for video ${numericVideoId}` ); const metadataId = await languageMetadataDAO.addLanguagedMetadata( numericVideoId, metadata ); peertubeHelpers.logger.info( `[transposer-connector] LanguageMetadataController: Successfully saved metadata for video ${numericVideoId} by user ${authUser.username}` ); return res.json({ metadataId }); } catch (error) { const idParam = req.params.id; peertubeHelpers.logger.error( `[transposer-connector] LanguageMetadataController: Exception in metadata POST endpoint for video ${idParam}: ${error.message}` ); return res.status(500).json({ error: "Internal server error" }); } }); }; module.exports = { initLanguageMetadata, };