peertube-plugin-transposer-connector
Version:
Transposer connector is a PeerTube language tool plugin to transcribe and translate with Whisper
483 lines (415 loc) • 16.4 kB
JavaScript
const { initRegisterSettings } = require("./settings.js");
const { LanguageMetadataDAO } = require("./server/dao/LanguageMetadataDAO.js");
const {
initTransposerController,
} = require("./server/controller/transposerController.js");
const {
initLanguageMetadata,
} = require("./server/controller/languageMetadataController.js");
const {
initCaptionMetadata,
} = require("./server/controller/captionMetadataController.js");
async function register({
getRouter,
registerHook,
registerSetting,
settingsManager,
peertubeHelpers,
}) {
peertubeHelpers.logger.info(
`[transposer-connector] Main: Starting plugin registration and initialization`
);
// init Settings
peertubeHelpers.logger.info(
`[transposer-connector] Main: Initializing plugin settings`
);
initRegisterSettings(registerSetting, peertubeHelpers);
// init DataAccessObjects
peertubeHelpers.logger.info(
`[transposer-connector] Main: Initializing data access objects`
);
var languageMetadataDAO = new LanguageMetadataDAO(peertubeHelpers);
peertubeHelpers.logger.info(
`[transposer-connector] Main: Successfully initialized LanguageMetadataDAO`
);
// init Controllers
peertubeHelpers.logger.info(
`[transposer-connector] Main: Initializing API controllers`
);
const router = getRouter();
initTransposerController(router, peertubeHelpers, settingsManager);
peertubeHelpers.logger.info(
`[transposer-connector] Main: TransposerController initialized`
);
initLanguageMetadata(router, languageMetadataDAO, peertubeHelpers);
peertubeHelpers.logger.info(
`[transposer-connector] Main: LanguageMetadataController initialized`
);
initCaptionMetadata(router, peertubeHelpers);
peertubeHelpers.logger.info(
`[transposer-connector] Main: CaptionMetadataController initialized`
);
// We also update the JSON LD context because we added a new field
// TODO: use https://www.w3.org/TR/json-ld11/#example-71-language-map-expressing-a-property-in-three-languages
// TODO: use https://docs.joinpeertube.org/contribute/plugins#federation
peertubeHelpers.logger.info(
`[transposer-connector] Main: Registering ActivityPub context build hook`
);
registerHook({
target: "filter:activity-pub.activity.context.build.result",
handler: (jsonld) => {
peertubeHelpers.logger.info(
`[transposer-connector] Main: ActivityPub context build hook triggered`
);
var newJsonLd = jsonld.concat([
{ translatedMetadata: "https://schema.org/recordedAt" },
]);
peertubeHelpers.logger.info(
`[transposer-connector] Main: Added translatedMetadata to ActivityPub context`
);
return jsonld;
},
});
//Triggers when a video is updated:
peertubeHelpers.logger.info(
`[transposer-connector] Main: Registering ActivityPub video JSON-LD build hook`
);
registerHook({
target: "filter:activity-pub.video.json-ld.build.result",
handler: async (jsonld, { video }) => {
const videoId = video.id;
peertubeHelpers.logger.info(
`[transposer-connector] Main: ActivityPub video JSON-LD build hook triggered for video ${videoId}`
);
var languageMetadata = await languageMetadataDAO.findLanguageMetadataById(
video.id
);
Object.assign(jsonld, { translatedMetadata: languageMetadata });
peertubeHelpers.logger.info(
`[transposer-connector] Main: Added language metadata to ActivityPub JSON-LD for video ${videoId}`
);
//TODO: get the videoCaptions
/*
const response = await fetch(
`https://tube.dev.displ.eu/api/v1/videos/${videoId}/captions`
);
const captions = await response.json();
peertubeHelpers.logger.info("captionsList", captions);
*/
return jsonld;
},
});
peertubeHelpers.logger.info(
`[transposer-connector] Main: Registering video uploaded hook`
);
registerHook({
target: "action:api.video.uploaded",
handler: async ({ video }) => {
if (!video) {
peertubeHelpers.logger.warn(
`[transposer-connector] Main: Video missing in uploaded hook`
);
return;
}
peertubeHelpers.logger.info(
`[transposer-connector] Main: Video ${video.id} uploaded - transposer only triggered on video update`
);
},
});
peertubeHelpers.logger.info(
`[transposer-connector] Main: Registering video updated hook`
);
registerHook({
target: "action:api.video.updated",
handler: async ({ video, body }) => {
peertubeHelpers.logger.info(
`[transposer-connector] Main: Video updated hook triggered`
);
if (!video) {
peertubeHelpers.logger.warn(
`[transposer-connector] Main: Video missing in updated hook`
);
return;
}
if (!body.pluginData) {
peertubeHelpers.logger.info(
`[transposer-connector] Main: No pluginData in video ${video.id} update - skipping transposer trigger`
);
return;
}
if (!body.pluginData["trigger-transposer"]) {
peertubeHelpers.logger.info(
`[transposer-connector] Main: trigger-transposer flag is false for video ${video.id} - skipping`
);
return;
}
peertubeHelpers.logger.info(
`[transposer-connector] Main: Triggering transposer for video ${video.id} via updated hook`
);
body.pluginData["trigger-transposer"] = false;
triggerTransposer(video);
},
});
async function triggerTransposer(video) {
peertubeHelpers.logger.info(
`[transposer-connector] TriggerTransposer: Starting transposer trigger for video ${video.id}`
);
const settings = await settingsManager.getSettings([
"trigger-transcription-and-translation",
]);
if (!settings["trigger-transcription-and-translation"]) {
peertubeHelpers.logger.warn(
`[transposer-connector] TriggerTransposer: Missing trigger-transcription-and-translation setting - aborting`
);
return;
}
peertubeHelpers.logger.info(
`[transposer-connector] TriggerTransposer: Found trigger-transcription-and-translation setting: ${settings["trigger-transcription-and-translation"]}`
);
const webserverUrl = peertubeHelpers.config.getWebserverUrl();
if (!webserverUrl) {
peertubeHelpers.logger.error(
`[transposer-connector] TriggerTransposer: Missing webserver URL configuration - cannot proceed`
);
return;
}
if (!video) {
peertubeHelpers.logger.error(
`[transposer-connector] TriggerTransposer: Video object is missing - cannot proceed`
);
return;
}
const { language, id } = video;
peertubeHelpers.logger.info(
`[transposer-connector] TriggerTransposer: Processing video ${id} with language ${
language || "undefined"
}`
);
if (!language) {
peertubeHelpers.logger.warn(
`[transposer-connector] TriggerTransposer: Video ${id} has no language defined - proceeding without language constraint`
);
}
peertubeHelpers.logger.info(
`[transposer-connector] TriggerTransposer: Fetching video files for video ${id}`
);
const videoFiles = await getVideoFiles(id, peertubeHelpers);
if (videoFiles.length === 0) {
peertubeHelpers.logger.warn(
`[transposer-connector] TriggerTransposer: No video files found for video ${id} - aborting transcription`
);
return;
}
peertubeHelpers.logger.info(
`[transposer-connector] TriggerTransposer: Found ${videoFiles.length} video file(s) for video ${id}`
);
peertubeHelpers.logger.info(
`[transposer-connector] TriggerTransposer: Sending transcription request for instance ${webserverUrl} to PeerTube Adapter`
);
if (videoFiles && videoFiles.length > 0 && videoFiles[0].url) {
var url = videoFiles[0].url; // TODO get the lowest video quality
peertubeHelpers.logger.info(
`[transposer-connector] TriggerTransposer: Selected video file URL: ${url}`
);
//not transcoded
if (url.includes("web-videos")) {
peertubeHelpers.logger.info(
`[transposer-connector] TriggerTransposer: Detected web-videos URL format - triggering transcription and translation`
);
triggerTranscriptionAndTranslation(
video.id,
url,
language,
settings["trigger-transcription-and-translation"],
webserverUrl
);
} else if (url.includes("streaming-playlist")) {
//transcoded
peertubeHelpers.logger.info(
`[transposer-connector] TriggerTransposer: Detected streaming-playlist URL format - triggering transcription and translation`
);
triggerTranscriptionAndTranslation(
video.id,
url,
language,
settings["trigger-transcription-and-translation"],
webserverUrl
);
} else {
peertubeHelpers.logger.info(
`[transposer-connector] TriggerTransposer: Unknown URL format detected - proceeding with transcription and translation`
);
peertubeHelpers.logger.info(
`[transposer-connector] TriggerTransposer: URL: ${url}`
);
triggerTranscriptionAndTranslation(
video.id,
url,
language,
settings["trigger-transcription-and-translation"],
webserverUrl
);
}
} else {
peertubeHelpers.logger.error(
`[transposer-connector] TriggerTransposer: Video files array is empty or first file has no valid URL - cannot proceed`
);
return;
}
peertubeHelpers.logger.info(
`[transposer-connector] TriggerTransposer: Completed transposer trigger process for video ${video.id}`
);
}
async function getVideoFiles(id, peertubeHelpers) {
peertubeHelpers.logger.info(
`[transposer-connector] GetVideoFiles: Starting video file retrieval for video ID ${id}`
);
const videoFilesData = await peertubeHelpers.videos.getFiles(id);
if (!videoFilesData) {
peertubeHelpers.logger.warn(
`[transposer-connector] GetVideoFiles: No video files data returned from API for video ID ${id}`
);
return [];
}
peertubeHelpers.logger.info(
`[transposer-connector] GetVideoFiles: Successfully retrieved video files data for video ID ${id}`
);
const webVideoFiles = videoFilesData.webVideo?.videoFiles || [];
const hlsVideoFiles = videoFilesData.hls?.videoFiles || [];
peertubeHelpers.logger.info(
`[transposer-connector] GetVideoFiles: Found ${webVideoFiles.length} web video files and ${hlsVideoFiles.length} HLS video files for video ID ${id}`
);
if (webVideoFiles.length === 0 && hlsVideoFiles.length === 0) {
peertubeHelpers.logger.warn(
`[transposer-connector] GetVideoFiles: No video files found in either web or HLS format for video ID ${id}`
);
return [];
}
const combinedVideoFiles = webVideoFiles.concat(hlsVideoFiles);
peertubeHelpers.logger.info(
`[transposer-connector] GetVideoFiles: Combined total of ${combinedVideoFiles.length} video files for video ID ${id}`
);
// Sortiere die Dateien nach Größe (aufsteigend)
const sortedVideoFiles = combinedVideoFiles.sort(
({ size: sizeA }, { size: sizeB }) => sizeA - sizeB
);
peertubeHelpers.logger.info(
`[transposer-connector] GetVideoFiles: Sorted video files by size (ascending) for video ID ${id} - smallest file size: ${
sortedVideoFiles[0]?.size || "unknown"
} bytes`
);
peertubeHelpers.logger.info(
`[transposer-connector] GetVideoFiles: Successfully completed video file retrieval for video ID ${id} - returning ${sortedVideoFiles.length} files`
);
return sortedVideoFiles;
}
function triggerTranscriptionAndTranslation(
videoId,
downloadUrl,
language,
triggerUrl,
webserverUrl
) {
peertubeHelpers.logger.info(
`[transposer-connector] TriggerTranscriptionAndTranslation: Starting transcription and translation trigger for video ${videoId}`
);
if (!triggerUrl) {
peertubeHelpers.logger.error(
`[transposer-connector] TriggerTranscriptionAndTranslation: Missing trigger URL - cannot proceed with video ${videoId}`
);
return;
}
peertubeHelpers.logger.info(
`[transposer-connector] TriggerTranscriptionAndTranslation: Using trigger URL: ${triggerUrl}`
);
let body = {
videoId: videoId,
url: downloadUrl,
peertubeInstanceBaseDomain: webserverUrl,
};
if (language) {
body.language = language;
peertubeHelpers.logger.info(
`[transposer-connector] TriggerTranscriptionAndTranslation: Including language ${language} in request for video ${videoId}`
);
} else {
peertubeHelpers.logger.info(
`[transposer-connector] TriggerTranscriptionAndTranslation: No language specified for video ${videoId} - proceeding without language constraint`
);
}
peertubeHelpers.logger.info(
`[transposer-connector] TriggerTranscriptionAndTranslation: Sending POST request to ${triggerUrl}/transcribe-and-translate/video for video ${videoId}`
);
peertubeHelpers.logger.info(
`[transposer-connector] TriggerTranscriptionAndTranslation: Request payload - videoId: ${videoId}, url: ${downloadUrl}, instance: ${webserverUrl}`
);
fetch(`${triggerUrl}/transcribe-and-translate/video`, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(body),
})
.then((response) => {
peertubeHelpers.logger.info(
`[transposer-connector] TriggerTranscriptionAndTranslation: Received response with status ${response.status} for video ${videoId}`
);
if (!response.ok) {
peertubeHelpers.logger.error(
`[transposer-connector] TriggerTranscriptionAndTranslation: HTTP error response ${response.status} for video ${videoId}`
);
response
.json()
.then((json_error) => {
peertubeHelpers.logger.error(
`[transposer-connector] TriggerTranscriptionAndTranslation: Error details for video ${videoId}: ${
json_error.message || "No error message provided"
}`
);
throw new Error(
`HTTP error! status: ${response.status} and message ${json_error.message}`
);
})
.catch((parseError) => {
peertubeHelpers.logger.error(
`[transposer-connector] TriggerTranscriptionAndTranslation: Failed to parse error response for video ${videoId}: ${parseError.message}`
);
throw new Error(`HTTP error! status: ${response.status}`);
});
}
return response.json();
})
.then((data) => {
const status = data.success ? "successful" : "unsuccessful";
peertubeHelpers.logger.info(
`[transposer-connector] TriggerTranscriptionAndTranslation: Transcription and translation request ${status} for video ${videoId}`
);
if (data.message) {
peertubeHelpers.logger.info(
`[transposer-connector] TriggerTranscriptionAndTranslation: Server response message for video ${videoId}: ${data.message}`
);
}
peertubeHelpers.logger.info(
`[transposer-connector] TriggerTranscriptionAndTranslation: Successfully completed request for video ${videoId}`
);
})
.catch((error) => {
peertubeHelpers.logger.error(
`[transposer-connector] TriggerTranscriptionAndTranslation: Failed to trigger transcription and translation for video ${videoId}: ${error.message}`
);
peertubeHelpers.logger.error(
`[transposer-connector] TriggerTranscriptionAndTranslation: Error details - URL: ${triggerUrl}, Video: ${videoId}, Error: ${
error.stack || error.message
}`
);
});
}
}
async function unregister() {
return;
}
module.exports = {
register,
unregister,
};