mirror of https://github.com/Chocobozzz/PeerTube
Run transcription after studio
parent
c5fa9fe70e
commit
e99e4aa93c
|
@ -13,7 +13,8 @@ import {
|
||||||
waitJobs
|
waitJobs
|
||||||
} from '@peertube/peertube-server-commands'
|
} from '@peertube/peertube-server-commands'
|
||||||
import { FIXTURE_URLS } from '@tests/shared/fixture-urls.js'
|
import { FIXTURE_URLS } from '@tests/shared/fixture-urls.js'
|
||||||
import { checkAutoCaption, checkLanguage, checkNoCaption, uploadForTranscription } from '@tests/shared/transcription.js'
|
import { checkAutoCaption, checkLanguage, checkNoCaption, getCaptionContent, uploadForTranscription } from '@tests/shared/transcription.js'
|
||||||
|
import { expect } from 'chai'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
|
|
||||||
describe('Test video transcription', function () {
|
describe('Test video transcription', function () {
|
||||||
|
@ -28,7 +29,8 @@ describe('Test video transcription', function () {
|
||||||
await setDefaultVideoChannel(servers)
|
await setDefaultVideoChannel(servers)
|
||||||
await doubleFollow(servers[0], servers[1])
|
await doubleFollow(servers[0], servers[1])
|
||||||
|
|
||||||
await waitJobs(servers)
|
await servers[0].config.enableTranscription()
|
||||||
|
|
||||||
await waitJobs(servers)
|
await waitJobs(servers)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -137,13 +139,67 @@ describe('Test video transcription', function () {
|
||||||
it('Should not run a transcription if the video does not contain audio', async function () {
|
it('Should not run a transcription if the video does not contain audio', async function () {
|
||||||
this.timeout(120000)
|
this.timeout(120000)
|
||||||
|
|
||||||
const uuid = await uploadForTranscription(servers[0], { generateTranscription: false })
|
const uuid = await uploadForTranscription(servers[0], { fixture: 'video_short_no_audio.mp4' })
|
||||||
|
|
||||||
await waitJobs(servers)
|
await waitJobs(servers)
|
||||||
await checkNoCaption(servers, uuid)
|
await checkNoCaption(servers, uuid)
|
||||||
await checkLanguage(servers, uuid, null)
|
await checkLanguage(servers, uuid, null)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Should run transcription after a video edition', async function () {
|
||||||
|
this.timeout(120000)
|
||||||
|
|
||||||
|
await servers[0].config.enableMinimumTranscoding()
|
||||||
|
await servers[0].config.enableStudio()
|
||||||
|
|
||||||
|
const uuid = await uploadForTranscription(servers[0])
|
||||||
|
await waitJobs(servers)
|
||||||
|
|
||||||
|
await checkAutoCaption(servers, uuid)
|
||||||
|
const oldContent = await getCaptionContent(servers[0], uuid, 'en')
|
||||||
|
|
||||||
|
await servers[0].videoStudio.createEditionTasks({
|
||||||
|
videoId: uuid,
|
||||||
|
tasks: [
|
||||||
|
{
|
||||||
|
name: 'cut' as 'cut',
|
||||||
|
options: { start: 1 }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
await waitJobs(servers)
|
||||||
|
await checkAutoCaption(servers, uuid)
|
||||||
|
|
||||||
|
const newContent = await getCaptionContent(servers[0], uuid, 'en')
|
||||||
|
expect(oldContent).to.not.equal(newContent)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should not run transcription after video edition if the subtitle has not been auto generated', async function () {
|
||||||
|
this.timeout(120000)
|
||||||
|
|
||||||
|
const uuid = await uploadForTranscription(servers[0], { language: 'en' })
|
||||||
|
await waitJobs(servers)
|
||||||
|
|
||||||
|
await servers[0].captions.add({ language: 'en', videoId: uuid, fixture: 'subtitle-good1.vtt' })
|
||||||
|
const oldContent = await getCaptionContent(servers[0], uuid, 'en')
|
||||||
|
|
||||||
|
await servers[0].videoStudio.createEditionTasks({
|
||||||
|
videoId: uuid,
|
||||||
|
tasks: [
|
||||||
|
{
|
||||||
|
name: 'cut' as 'cut',
|
||||||
|
options: { start: 1 }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
await waitJobs(servers)
|
||||||
|
|
||||||
|
const newContent = await getCaptionContent(servers[0], uuid, 'en')
|
||||||
|
expect(oldContent).to.equal(newContent)
|
||||||
|
})
|
||||||
|
|
||||||
after(async function () {
|
after(async function () {
|
||||||
await cleanupTests(servers)
|
await cleanupTests(servers)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
||||||
|
|
||||||
import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
|
import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
|
||||||
import { PeerTubeServer, VideoEdit } from '@peertube/peertube-server-commands'
|
import { makeGetRequest, PeerTubeServer, VideoEdit } from '@peertube/peertube-server-commands'
|
||||||
import { downloadFile, unzip } from '@peertube/peertube-transcription-devtools'
|
import { downloadFile, unzip } from '@peertube/peertube-transcription-devtools'
|
||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import { ensureDir, pathExists } from 'fs-extra/esm'
|
import { ensureDir, pathExists } from 'fs-extra/esm'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { testCaptionFile } from './captions.js'
|
import { testCaptionFile } from './captions.js'
|
||||||
import { FIXTURE_URLS } from './fixture-urls.js'
|
import { FIXTURE_URLS } from './fixture-urls.js'
|
||||||
|
import { HttpStatusCode } from '../../../models/src/http/http-status-codes.js'
|
||||||
|
|
||||||
type CustomModelName = 'tiny.pt' | 'faster-whisper-tiny'
|
type CustomModelName = 'tiny.pt' | 'faster-whisper-tiny'
|
||||||
|
|
||||||
|
@ -57,6 +58,16 @@ export async function checkNoCaption (servers: PeerTubeServer[], uuid: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getCaptionContent (server: PeerTubeServer, videoId: string, language: string) {
|
||||||
|
const { data } = await server.captions.list({ videoId })
|
||||||
|
|
||||||
|
const caption = data.find(c => c.language.id === language)
|
||||||
|
|
||||||
|
const { text } = await makeGetRequest({ url: server.url, path: caption.captionPath, expectedStatus: HttpStatusCode.OK_200 })
|
||||||
|
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export async function checkLanguage (servers: PeerTubeServer[], uuid: string, expected: string | null) {
|
export async function checkLanguage (servers: PeerTubeServer[], uuid: string, expected: string | null) {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { ffprobePromise, getAudioStream, getVideoStreamDimensionsInfo, getVideoStreamFPS } from '@peertube/peertube-ffmpeg'
|
import { ffprobePromise, getAudioStream, getVideoStreamDimensionsInfo, getVideoStreamFPS } from '@peertube/peertube-ffmpeg'
|
||||||
import { ThumbnailType, VideoLiveEndingPayload, VideoState } from '@peertube/peertube-models'
|
import { ThumbnailType, VideoLiveEndingPayload, VideoState } from '@peertube/peertube-models'
|
||||||
import { peertubeTruncate } from '@server/helpers/core-utils.js'
|
import { peertubeTruncate } from '@server/helpers/core-utils.js'
|
||||||
import { CONFIG } from '@server/initializers/config.js'
|
|
||||||
import { CONSTRAINTS_FIELDS } from '@server/initializers/constants.js'
|
import { CONSTRAINTS_FIELDS } from '@server/initializers/constants.js'
|
||||||
import { getLocalVideoActivityPubUrl } from '@server/lib/activitypub/url.js'
|
import { getLocalVideoActivityPubUrl } from '@server/lib/activitypub/url.js'
|
||||||
import { federateVideoIfNeeded } from '@server/lib/activitypub/videos/index.js'
|
import { federateVideoIfNeeded } from '@server/lib/activitypub/videos/index.js'
|
||||||
|
@ -177,10 +176,7 @@ async function saveReplayToExternalVideo (options: {
|
||||||
}
|
}
|
||||||
|
|
||||||
await createStoryboardJob(replayVideo)
|
await createStoryboardJob(replayVideo)
|
||||||
|
await createTranscriptionTaskIfNeeded(replayVideo)
|
||||||
if (CONFIG.VIDEO_TRANSCRIPTION.ENABLED === true) {
|
|
||||||
await createTranscriptionTaskIfNeeded(replayVideo)
|
|
||||||
}
|
|
||||||
|
|
||||||
await moveToNextState({ video: replayVideo, isNewVideo: true })
|
await moveToNextState({ video: replayVideo, isNewVideo: true })
|
||||||
}
|
}
|
||||||
|
@ -251,10 +247,7 @@ async function replaceLiveByReplay (options: {
|
||||||
await moveToNextState({ video: videoWithFiles, isNewVideo: true })
|
await moveToNextState({ video: videoWithFiles, isNewVideo: true })
|
||||||
|
|
||||||
await createStoryboardJob(videoWithFiles)
|
await createStoryboardJob(videoWithFiles)
|
||||||
|
await createTranscriptionTaskIfNeeded(videoWithFiles)
|
||||||
if (CONFIG.VIDEO_TRANSCRIPTION.ENABLED === true) {
|
|
||||||
await createTranscriptionTaskIfNeeded(videoWithFiles)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function assignReplayFilesToVideo (options: {
|
async function assignReplayFilesToVideo (options: {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { VideoCaptionModel } from '@server/models/video/video-caption.js'
|
||||||
import { VideoJobInfoModel } from '@server/models/video/video-job-info.js'
|
import { VideoJobInfoModel } from '@server/models/video/video-job-info.js'
|
||||||
import { VideoModel } from '@server/models/video/video.js'
|
import { VideoModel } from '@server/models/video/video.js'
|
||||||
import { MVideo, MVideoCaption, MVideoFullLight, MVideoUUID, MVideoUrl } from '@server/types/models/index.js'
|
import { MVideo, MVideoCaption, MVideoFullLight, MVideoUUID, MVideoUrl } from '@server/types/models/index.js'
|
||||||
|
import { MutexInterface } from 'async-mutex'
|
||||||
import { ensureDir, remove } from 'fs-extra/esm'
|
import { ensureDir, remove } from 'fs-extra/esm'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { federateVideoIfNeeded } from './activitypub/videos/federate.js'
|
import { federateVideoIfNeeded } from './activitypub/videos/federate.js'
|
||||||
|
@ -18,7 +19,6 @@ import { JobQueue } from './job-queue/job-queue.js'
|
||||||
import { Notifier } from './notifier/notifier.js'
|
import { Notifier } from './notifier/notifier.js'
|
||||||
import { TranscriptionJobHandler } from './runners/index.js'
|
import { TranscriptionJobHandler } from './runners/index.js'
|
||||||
import { VideoPathManager } from './video-path-manager.js'
|
import { VideoPathManager } from './video-path-manager.js'
|
||||||
import { MutexInterface } from 'async-mutex'
|
|
||||||
|
|
||||||
const lTags = loggerTagsFactory('video-caption')
|
const lTags = loggerTagsFactory('video-caption')
|
||||||
|
|
||||||
|
|
|
@ -109,7 +109,7 @@ export async function addVideoJobsAfterCreation (options: {
|
||||||
|
|
||||||
await JobQueue.Instance.createSequentialJobFlow(...jobs)
|
await JobQueue.Instance.createSequentialJobFlow(...jobs)
|
||||||
|
|
||||||
if (generateTranscription === true && CONFIG.VIDEO_TRANSCRIPTION.ENABLED === true) {
|
if (generateTranscription === true) {
|
||||||
await createTranscriptionTaskIfNeeded(video)
|
await createTranscriptionTaskIfNeeded(video)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
import { move, remove } from 'fs-extra/esm'
|
import { buildAspectRatio } from '@peertube/peertube-core-utils'
|
||||||
import { join } from 'path'
|
import { getVideoStreamDuration } from '@peertube/peertube-ffmpeg'
|
||||||
|
import { VideoStudioEditionPayload, VideoStudioTask, VideoStudioTaskPayload } from '@peertube/peertube-models'
|
||||||
import { logger, loggerTagsFactory } from '@server/helpers/logger.js'
|
import { logger, loggerTagsFactory } from '@server/helpers/logger.js'
|
||||||
import { createTorrentAndSetInfoHashFromPath } from '@server/helpers/webtorrent.js'
|
import { createTorrentAndSetInfoHashFromPath } from '@server/helpers/webtorrent.js'
|
||||||
import { CONFIG } from '@server/initializers/config.js'
|
import { CONFIG } from '@server/initializers/config.js'
|
||||||
|
import { VideoCaptionModel } from '@server/models/video/video-caption.js'
|
||||||
import { MUser, MVideo, MVideoFile, MVideoFullLight, MVideoWithAllFiles } from '@server/types/models/index.js'
|
import { MUser, MVideo, MVideoFile, MVideoFullLight, MVideoWithAllFiles } from '@server/types/models/index.js'
|
||||||
import { getVideoStreamDuration } from '@peertube/peertube-ffmpeg'
|
import { move, remove } from 'fs-extra/esm'
|
||||||
import { VideoStudioEditionPayload, VideoStudioTask, VideoStudioTaskPayload } from '@peertube/peertube-models'
|
import { join } from 'path'
|
||||||
import { JobQueue } from './job-queue/index.js'
|
import { JobQueue } from './job-queue/index.js'
|
||||||
import { VideoStudioTranscodingJobHandler } from './runners/index.js'
|
import { VideoStudioTranscodingJobHandler } from './runners/index.js'
|
||||||
import { getTranscodingJobPriority } from './transcoding/transcoding-priority.js'
|
import { getTranscodingJobPriority } from './transcoding/transcoding-priority.js'
|
||||||
|
import { createTranscriptionTaskIfNeeded } from './video-captions.js'
|
||||||
import { buildNewFile, removeHLSPlaylist, removeWebVideoFile } from './video-file.js'
|
import { buildNewFile, removeHLSPlaylist, removeWebVideoFile } from './video-file.js'
|
||||||
import { VideoPathManager } from './video-path-manager.js'
|
|
||||||
import { buildStoryboardJobIfNeeded } from './video-jobs.js'
|
import { buildStoryboardJobIfNeeded } from './video-jobs.js'
|
||||||
import { buildAspectRatio } from '@peertube/peertube-core-utils'
|
import { VideoPathManager } from './video-path-manager.js'
|
||||||
|
|
||||||
const lTags = loggerTagsFactory('video-studio')
|
const lTags = loggerTagsFactory('video-studio')
|
||||||
|
|
||||||
|
@ -108,7 +110,7 @@ export async function onVideoStudioEnded (options: {
|
||||||
video.aspectRatio = buildAspectRatio({ width: newFile.width, height: newFile.height })
|
video.aspectRatio = buildAspectRatio({ width: newFile.width, height: newFile.height })
|
||||||
await video.save()
|
await video.save()
|
||||||
|
|
||||||
return JobQueue.Instance.createSequentialJobFlow(
|
await JobQueue.Instance.createSequentialJobFlow(
|
||||||
buildStoryboardJobIfNeeded({ video, federate: false }),
|
buildStoryboardJobIfNeeded({ video, federate: false }),
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -129,6 +131,14 @@ export async function onVideoStudioEnded (options: {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (video.language && CONFIG.VIDEO_TRANSCRIPTION.ENABLED) {
|
||||||
|
const caption = await VideoCaptionModel.loadByVideoIdAndLanguage(video.id, video.language)
|
||||||
|
|
||||||
|
if (caption?.automaticallyGenerated) {
|
||||||
|
await createTranscriptionTaskIfNeeded(video)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
Loading…
Reference in New Issue