chore(server): introduce proper job status (#7932)
* introduce proper job status * fix condition for onDone jobs * fix tests
This commit is contained in:
@@ -34,6 +34,7 @@ import {
|
||||
IPersonRepository,
|
||||
IStorageRepository,
|
||||
ISystemConfigRepository,
|
||||
JobStatus,
|
||||
WithoutProperty,
|
||||
} from '../repositories';
|
||||
import { MediaService } from './media.service';
|
||||
@@ -1214,22 +1215,22 @@ describe(MediaService.name, () => {
|
||||
expect(mediaMock.transcode).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return false if hwaccel is enabled for an unsupported codec', async () => {
|
||||
it('should fail if hwaccel is enabled for an unsupported codec', async () => {
|
||||
mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
|
||||
configMock.load.mockResolvedValue([
|
||||
{ key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.NVENC },
|
||||
{ key: SystemConfigKey.FFMPEG_TARGET_VIDEO_CODEC, value: VideoCodec.VP9 },
|
||||
]);
|
||||
assetMock.getByIds.mockResolvedValue([assetStub.video]);
|
||||
await expect(sut.handleVideoConversion({ id: assetStub.video.id })).resolves.toEqual(false);
|
||||
await expect(sut.handleVideoConversion({ id: assetStub.video.id })).resolves.toBe(JobStatus.FAILED);
|
||||
expect(mediaMock.transcode).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return false if hwaccel option is invalid', async () => {
|
||||
it('should fail if hwaccel option is invalid', async () => {
|
||||
mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
|
||||
configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_ACCEL, value: 'invalid' }]);
|
||||
assetMock.getByIds.mockResolvedValue([assetStub.video]);
|
||||
await expect(sut.handleVideoConversion({ id: assetStub.video.id })).resolves.toEqual(false);
|
||||
await expect(sut.handleVideoConversion({ id: assetStub.video.id })).resolves.toBe(JobStatus.FAILED);
|
||||
expect(mediaMock.transcode).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -1548,12 +1549,12 @@ describe(MediaService.name, () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should return false for qsv if no hw devices', async () => {
|
||||
it('should fail for qsv if no hw devices', async () => {
|
||||
storageMock.readdir.mockResolvedValue([]);
|
||||
mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
|
||||
configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.QSV }]);
|
||||
assetMock.getByIds.mockResolvedValue([assetStub.video]);
|
||||
await expect(sut.handleVideoConversion({ id: assetStub.video.id })).resolves.toEqual(false);
|
||||
await expect(sut.handleVideoConversion({ id: assetStub.video.id })).resolves.toBe(JobStatus.FAILED);
|
||||
expect(mediaMock.transcode).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -1777,12 +1778,12 @@ describe(MediaService.name, () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should return false for vaapi if no hw devices', async () => {
|
||||
it('should fail for vaapi if no hw devices', async () => {
|
||||
storageMock.readdir.mockResolvedValue([]);
|
||||
mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
|
||||
configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.VAAPI }]);
|
||||
assetMock.getByIds.mockResolvedValue([assetStub.video]);
|
||||
await expect(sut.handleVideoConversion({ id: assetStub.video.id })).resolves.toEqual(false);
|
||||
await expect(sut.handleVideoConversion({ id: assetStub.video.id })).resolves.toBe(JobStatus.FAILED);
|
||||
expect(mediaMock.transcode).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
IStorageRepository,
|
||||
ISystemConfigRepository,
|
||||
JobItem,
|
||||
JobStatus,
|
||||
VideoCodecHWConfig,
|
||||
VideoStreamInfo,
|
||||
WithoutProperty,
|
||||
@@ -70,7 +71,7 @@ export class MediaService {
|
||||
);
|
||||
}
|
||||
|
||||
async handleQueueGenerateThumbnails({ force }: IBaseJob) {
|
||||
async handleQueueGenerateThumbnails({ force }: IBaseJob): Promise<JobStatus> {
|
||||
const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => {
|
||||
return force
|
||||
? this.assetRepository.getAll(pagination)
|
||||
@@ -118,10 +119,10 @@ export class MediaService {
|
||||
|
||||
await this.jobRepository.queueAll(jobs);
|
||||
|
||||
return true;
|
||||
return JobStatus.SUCCESS;
|
||||
}
|
||||
|
||||
async handleQueueMigration() {
|
||||
async handleQueueMigration(): Promise<JobStatus> {
|
||||
const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) =>
|
||||
this.assetRepository.getAll(pagination),
|
||||
);
|
||||
@@ -148,31 +149,31 @@ export class MediaService {
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
return JobStatus.SUCCESS;
|
||||
}
|
||||
|
||||
async handleAssetMigration({ id }: IEntityJob) {
|
||||
async handleAssetMigration({ id }: IEntityJob): Promise<JobStatus> {
|
||||
const [asset] = await this.assetRepository.getByIds([id]);
|
||||
if (!asset) {
|
||||
return false;
|
||||
return JobStatus.FAILED;
|
||||
}
|
||||
|
||||
await this.storageCore.moveAssetFile(asset, AssetPathType.JPEG_THUMBNAIL);
|
||||
await this.storageCore.moveAssetFile(asset, AssetPathType.WEBP_THUMBNAIL);
|
||||
await this.storageCore.moveAssetFile(asset, AssetPathType.ENCODED_VIDEO);
|
||||
|
||||
return true;
|
||||
return JobStatus.SUCCESS;
|
||||
}
|
||||
|
||||
async handleGenerateJpegThumbnail({ id }: IEntityJob) {
|
||||
async handleGenerateJpegThumbnail({ id }: IEntityJob): Promise<JobStatus> {
|
||||
const [asset] = await this.assetRepository.getByIds([id], { exifInfo: true });
|
||||
if (!asset) {
|
||||
return false;
|
||||
return JobStatus.FAILED;
|
||||
}
|
||||
|
||||
const resizePath = await this.generateThumbnail(asset, 'jpeg');
|
||||
await this.assetRepository.save({ id: asset.id, resizePath });
|
||||
return true;
|
||||
return JobStatus.SUCCESS;
|
||||
}
|
||||
|
||||
private async generateThumbnail(asset: AssetEntity, format: 'jpeg' | 'webp') {
|
||||
@@ -214,30 +215,30 @@ export class MediaService {
|
||||
return path;
|
||||
}
|
||||
|
||||
async handleGenerateWebpThumbnail({ id }: IEntityJob) {
|
||||
async handleGenerateWebpThumbnail({ id }: IEntityJob): Promise<JobStatus> {
|
||||
const [asset] = await this.assetRepository.getByIds([id], { exifInfo: true });
|
||||
if (!asset) {
|
||||
return false;
|
||||
return JobStatus.FAILED;
|
||||
}
|
||||
|
||||
const webpPath = await this.generateThumbnail(asset, 'webp');
|
||||
await this.assetRepository.save({ id: asset.id, webpPath });
|
||||
return true;
|
||||
return JobStatus.SUCCESS;
|
||||
}
|
||||
|
||||
async handleGenerateThumbhashThumbnail({ id }: IEntityJob): Promise<boolean> {
|
||||
async handleGenerateThumbhashThumbnail({ id }: IEntityJob): Promise<JobStatus> {
|
||||
const [asset] = await this.assetRepository.getByIds([id]);
|
||||
if (!asset?.resizePath) {
|
||||
return false;
|
||||
return JobStatus.FAILED;
|
||||
}
|
||||
|
||||
const thumbhash = await this.mediaRepository.generateThumbhash(asset.resizePath);
|
||||
await this.assetRepository.save({ id: asset.id, thumbhash });
|
||||
|
||||
return true;
|
||||
return JobStatus.SUCCESS;
|
||||
}
|
||||
|
||||
async handleQueueVideoConversion(job: IBaseJob) {
|
||||
async handleQueueVideoConversion(job: IBaseJob): Promise<JobStatus> {
|
||||
const { force } = job;
|
||||
|
||||
const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => {
|
||||
@@ -252,13 +253,13 @@ export class MediaService {
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
return JobStatus.SUCCESS;
|
||||
}
|
||||
|
||||
async handleVideoConversion({ id }: IEntityJob) {
|
||||
async handleVideoConversion({ id }: IEntityJob): Promise<JobStatus> {
|
||||
const [asset] = await this.assetRepository.getByIds([id]);
|
||||
if (!asset || asset.type !== AssetType.VIDEO) {
|
||||
return false;
|
||||
return JobStatus.FAILED;
|
||||
}
|
||||
|
||||
const input = asset.originalPath;
|
||||
@@ -270,12 +271,12 @@ export class MediaService {
|
||||
const mainAudioStream = this.getMainStream(audioStreams);
|
||||
const containerExtension = format.formatName;
|
||||
if (!mainVideoStream || !containerExtension) {
|
||||
return false;
|
||||
return JobStatus.FAILED;
|
||||
}
|
||||
|
||||
if (!mainVideoStream.height || !mainVideoStream.width) {
|
||||
this.logger.warn(`Skipped transcoding for asset ${asset.id}: no video streams found`);
|
||||
return false;
|
||||
return JobStatus.FAILED;
|
||||
}
|
||||
|
||||
const { ffmpeg: config } = await this.configCore.getConfig();
|
||||
@@ -288,7 +289,7 @@ export class MediaService {
|
||||
await this.assetRepository.save({ id: asset.id, encodedVideoPath: null });
|
||||
}
|
||||
|
||||
return true;
|
||||
return JobStatus.SKIPPED;
|
||||
}
|
||||
|
||||
let transcodeOptions;
|
||||
@@ -298,7 +299,7 @@ export class MediaService {
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(`An error occurred while configuring transcoding options: ${error}`);
|
||||
return false;
|
||||
return JobStatus.FAILED;
|
||||
}
|
||||
|
||||
this.logger.log(`Started encoding video ${asset.id} ${JSON.stringify(transcodeOptions)}`);
|
||||
@@ -322,7 +323,7 @@ export class MediaService {
|
||||
|
||||
await this.assetRepository.save({ id: asset.id, encodedVideoPath: output });
|
||||
|
||||
return true;
|
||||
return JobStatus.SUCCESS;
|
||||
}
|
||||
|
||||
private getMainStream<T extends VideoStreamInfo | AudioStreamInfo>(streams: T[]): T {
|
||||
|
||||
Reference in New Issue
Block a user