peertube-plugin-transposer-connector
Version:
Transposer connector is a PeerTube language tool plugin to transcribe and translate with Whisper
551 lines (478 loc) • 18.3 kB
JavaScript
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))) {
return Number(idParam);
}
try {
// 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) {
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) {
return data.id;
}
throw new Error("Video ID not found in response");
} catch (error) {
console.error("Error fetching video details:", 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 {
console.log("started /language/metadata/video/:id");
const idParam = req.params.id;
console.log("ID parameter:", idParam);
// Get the numeric ID
let numericVideoId;
try {
numericVideoId = await getVideoNumericId(idParam);
console.log("Resolved to numeric ID:", numericVideoId);
} catch (error) {
return res.status(404).json({ error: "Video not found" });
}
const languages = req.query.languages
? req.query.languages.split(",")
: [];
console.log("languages:", languages);
const attributes = req.query.attribute
? req.query.attribute.split(",")
: [];
console.log("attributes:", attributes);
const completeMetadata =
await languageMetadataDAO.findLanguageMetadataByIdAndLanguages(
numericVideoId,
languages
);
if (!completeMetadata) {
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;
}
}
});
return res.json(transformedObject);
} catch (error) {
console.error("Error processing request:", error);
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 {
console.log("started /language/metadata/video/:id/:attribute");
const idParam = req.params.id;
const attribute = req.params.attribute;
// Get the numeric ID
let numericVideoId;
try {
numericVideoId = await getVideoNumericId(idParam);
console.log("Resolved to numeric ID:", numericVideoId);
} catch (error) {
return res.status(404).json({ error: "Video not found" });
}
const specificMetadata =
await languageMetadataDAO.findSpecificLanguageMetadata(
numericVideoId,
attribute
);
if (!specificMetadata) {
return res
.status(404)
.json({ error: "Not Found: Resource not available" });
}
const transformedObject = {};
specificMetadata.forEach((item) => {
transformedObject[item.language] = item.value;
});
return res.json(transformedObject);
} catch (error) {
console.error("Error processing request:", error);
return res.status(500).json({ error: "Internal server error" });
}
});
router.get("/video/:id/complete", async (req, res) => {
try {
console.log("started /video/:id/complete");
const idParam = req.params.id;
console.log("ID parameter:", idParam);
// Get the numeric ID
let numericVideoId;
try {
numericVideoId = await getVideoNumericId(idParam);
console.log("Resolved to numeric ID:", numericVideoId);
} catch (error) {
return res.status(404).json({ error: "Video not found" });
}
// Get query parameters for filtering
const languages = req.query.languages
? req.query.languages.split(",")
: [];
console.log("languages filter:", languages);
const attributes = req.query.attribute
? req.query.attribute.split(",")
: [];
console.log("attributes filter:", attributes);
// 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;
}
} catch (error) {
console.error("Error fetching metadata:", error);
metadata = {};
}
// Fetch captions
let captions = [];
try {
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) {
captions = captions.filter((caption) =>
languages.includes(caption.language.id)
);
}
} else {
console.warn(`Failed to fetch captions: ${captionsResponse.status}`);
}
} catch (error) {
console.error("Error fetching captions:", error);
// Continue with empty captions rather than failing completely
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),
},
};
return res.json(combinedData);
} catch (error) {
console.error("Error processing combined video data request:", error);
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 {
console.log("started /video/:id/complete-with-content");
const idParam = req.params.id;
console.log("ID parameter:", idParam);
// Get the numeric ID
let numericVideoId;
try {
numericVideoId = await getVideoNumericId(idParam);
console.log("Resolved to numeric ID:", numericVideoId);
} catch (error) {
return res.status(404).json({ error: "Video not found" });
}
// Get query parameters for filtering
const languages = req.query.languages
? req.query.languages.split(",")
: [];
console.log("languages filter:", languages);
const attributes = req.query.attribute
? req.query.attribute.split(",")
: [];
console.log("attributes filter:", attributes);
// 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;
}
} catch (error) {
console.error("Error fetching metadata:", error);
metadata = {};
}
// Fetch captions with content
let captions = [];
try {
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) {
captionsList = captionsList.filter((caption) =>
languages.includes(caption.language.id)
);
}
// Fetch content for each caption
for (const caption of captionsList) {
try {
const captionContentResponse = await fetch(
`${webserverUrl}${caption.captionPath}`
);
if (captionContentResponse.ok) {
caption.content = await captionContentResponse.text();
} else {
caption.content = null;
caption.contentError = `Failed to fetch content: ${captionContentResponse.status}`;
}
} catch (error) {
caption.content = null;
caption.contentError = `Error fetching content: ${error.message}`;
}
}
captions = captionsList;
} else {
console.warn(`Failed to fetch captions: ${captionsResponse.status}`);
}
} catch (error) {
console.error("Error fetching captions:", error);
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),
},
};
return res.json(combinedData);
} catch (error) {
console.error(
"Error processing combined video data with content request:",
error
);
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 {
console.log("Starting metadata POST endpoint processing");
console.log("res");
console.log(res);
let authUser;
try {
console.log("Attempting to get authenticated user");
authUser = await peertubeHelpers.user.getAuthUser(res);
console.log(
"Auth user result:",
authUser ? `Found: ${authUser.username}` : "No user found"
);
if (!authUser) {
console.log("Authentication failed: No user found");
return res.status(401).json({ error: "Authentication required" });
}
console.log("Authenticated user ID:", authUser.id);
console.log("Authenticated user username:", authUser.username);
console.log("Authenticated user role:", authUser.role);
console.log(
"Authenticated user account:",
authUser.Account ? authUser.Account.id : "No account info"
);
} catch (error) {
console.error("Authentication error details:", error);
console.error("Error stack:", error.stack);
return res.status(401).json({ error: "Authentication failed" });
}
const idParam = req.params.id;
console.log("Video ID from params:", idParam);
const metadata = req.body;
console.log("Metadata validation starting");
if (!metadata || typeof metadata !== "object") {
console.log("Invalid metadata format:", metadata);
return res.status(400).json({ error: "Invalid metadata format" });
}
console.log("Metadata validation passed");
let numericVideoId;
try {
console.log("Resolving video ID to numeric ID");
numericVideoId = await getVideoNumericId(idParam);
console.log("Resolved to numeric ID:", numericVideoId);
} catch (error) {
console.error("Error resolving video ID:", error);
return res.status(404).json({ error: "Video not found" });
}
let video;
try {
console.log("Loading video with ID:", numericVideoId);
console.log(
"Video loader function:",
typeof peertubeHelpers.videos.loadByIdOrUUID
);
video = await peertubeHelpers.videos.loadByIdOrUUID(numericVideoId);
console.log("Video load result:", video ? "Success" : "Not found");
if (!video) {
console.log("Video not found with ID:", numericVideoId);
return res.status(404).json({ error: "Video not found" });
}
console.log("Video object keys:", Object.keys(video));
console.log("Video name:", video.name);
console.log(
"Video channel:",
video.VideoChannel ? video.VideoChannel.name : "No channel info"
);
if (video.VideoChannel) {
console.log(
"Video channel account:",
video.VideoChannel.Account
? video.VideoChannel.Account.id
: "No account info"
);
}
} catch (error) {
console.error("Error loading video details:", error);
console.error("Error stack:", error.stack);
return res.status(500).json({ error: "Failed to load video details" });
}
console.log("Checking video ownership");
console.log("Video channel account ID:", video.VideoChannel?.Account?.id);
console.log("Auth user account ID:", authUser.Account?.id);
const isVideoOwner =
video.VideoChannel?.Account?.id === authUser.Account?.id;
const isAdmin = authUser.role === 0;
console.log("Is video owner?", isVideoOwner);
console.log("Is admin?", isAdmin);
if (!isVideoOwner && !isAdmin) {
console.log("Permission denied: User is not video owner or admin");
return res.status(403).json({
error: "You don't have permission to modify this video's metadata",
});
}
console.log("Permission granted, processing metadata");
console.log("Languages in metadata:", Object.keys(metadata));
Object.keys(metadata).forEach((langCode) => {
console.log(`Processing language: ${langCode}`);
if (typeof metadata[langCode] !== "object") {
console.log(
`Invalid format for language '${langCode}'`,
metadata[langCode]
);
return res.status(400).json({
error: `Invalid format for language '${langCode}'`,
});
}
console.log(
`Fields for language ${langCode}:`,
Object.keys(metadata[langCode])
);
Object.keys(metadata[langCode]).forEach((field) => {
console.log(`Processing field '${field}' for language '${langCode}'`);
console.log(`Original value:`, metadata[langCode][field]);
if (
metadata[langCode][field] === null ||
metadata[langCode][field] === undefined
) {
console.log(
`Empty value found for '${field}', setting to empty string`
);
metadata[langCode][field] = "";
}
metadata[langCode][field] = String(metadata[langCode][field]).trim();
console.log(`Final value:`, metadata[langCode][field]);
});
});
console.log("Saving metadata to database");
const metadataId = await languageMetadataDAO.addLanguagedMetadata(
numericVideoId,
metadata
);
console.log("Metadata saved successfully with ID:", metadataId);
console.log(
`User ${authUser.username} updated metadata for video ${numericVideoId} (original ID: ${idParam})`
);
return res.json({ metadataId });
} catch (error) {
console.error("Error in metadata endpoint:", error);
console.error("Error stack:", error.stack);
return res.status(500).json({ error: "Internal server error" });
}
});
};
module.exports = {
initLanguageMetadata,
};