Skip to content

Commit

Permalink
feat: add nfo-parse to watch service
Browse files Browse the repository at this point in the history
  • Loading branch information
hqwuzhaoyi committed Nov 19, 2023
1 parent 79758e2 commit 04be5eb
Show file tree
Hide file tree
Showing 13 changed files with 540 additions and 153 deletions.
1 change: 1 addition & 0 deletions apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"global-agent": "^3.0.0",
"multer": "1.4.5-lts.1",
"mysql2": "^3.6.2",
"nfo-parser": "workspace:^",
"openai": "^4.17.0",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1",
Expand Down
50 changes: 40 additions & 10 deletions apps/server/src/files/entities/file.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,18 @@ export class FileEntity {
@PrimaryGeneratedColumn()
id: number;

@Column({ length: 500 })
@Column({ length: 500, nullable: true })
fileName: string;

@Column({ length: 500 })
@Column({ length: 500, nullable: true })
baseName: string;
@Column({ length: 500 })
@Column({ length: 500, nullable: true })
extName: string;

@Column("text")
@Column({ length: 500 })
filePath: string;

@Column()
@Column({ nullable: true })
status: string;
}

Expand All @@ -38,13 +38,11 @@ export class VideoFileEntity extends FileEntity {
)
audioFile: Promise<AudioFileEntity>;

@Column({ length: 500, nullable: true })
fanart: string;
@Column({ length: 500, nullable: true })
poster: string;

@OneToMany(() => SubtitleFileEntity, (subtitleFile) => subtitleFile.audioFile)
subtitleFiles: SubtitleFileEntity[];

@OneToOne("NfoFileEntity", (nfoFile: NfoFileEntity) => nfoFile.videoFile)
nfoFile: Promise<NfoFileEntity>;
}

@Entity()
Expand All @@ -65,3 +63,35 @@ export class SubtitleFileEntity extends FileEntity {
@ManyToOne(() => VideoFileEntity, (videoFile) => videoFile.subtitleFiles)
videoFile: VideoFileEntity;
}

@Entity()
export class NfoFileEntity extends FileEntity {
@Column({ length: 500, nullable: true })
title: string;

@Column({ length: 500, nullable: true })
originaltitle: string;

@Column("text", { nullable: true })
plot: string;

@Column({ length: 500, nullable: true })
poster: string;

@Column({ length: 500, nullable: true })
fanart: string;

@Column("json", { nullable: true })
actors: {
name: string;
role: string;
thumb: string;
}[];

@Column({nullable: true})
dateadded: string;

@OneToOne(() => VideoFileEntity)
@JoinColumn()
videoFile: VideoFileEntity;
}
105 changes: 7 additions & 98 deletions apps/server/src/files/watch/watch.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
VideoFileEntity,
AudioFileEntity,
SubtitleFileEntity,
NfoFileEntity,
} from "../entities/file.entity";
import { CreateWatchDto } from "./dto/create-watch.dto";
import * as fs from "fs";
Expand All @@ -24,6 +25,7 @@ describe("WatchService", () => {
let mockVideoFileRepo: Partial<Repository<VideoFileEntity>>;
let mockAudioFileRepo: Partial<Repository<AudioFileEntity>>;
let mockSubtitleFileRepo: Partial<Repository<SubtitleFileEntity>>;
let mockNfoFileRepo: Partial<Repository<NfoFileEntity>>;

beforeEach(async () => {
mockQueue = {
Expand Down Expand Up @@ -65,6 +67,10 @@ describe("WatchService", () => {
provide: getRepositoryToken(SubtitleFileEntity),
useValue: mockSubtitleFileRepo,
},
{
provide: getRepositoryToken(NfoFileEntity),
useValue: mockNfoFileRepo,
},
{
provide: "BullQueue_audio",
useValue: mockQueue,
Expand Down Expand Up @@ -98,23 +104,19 @@ describe("WatchService", () => {
const checkNfoFileSpy = jest
.spyOn(service, "checkNfoFile")
.mockReturnValue(true);
const updateVideoImageSpy = jest
.spyOn(service, "updateVideoImage")
.mockImplementation(() => null);

// Act
await service.onModuleInit();

// Assert
expect(watchFilesSpy).toHaveBeenCalled();
expect(enqueueFileProcessingJobSpy).toHaveBeenCalled();
expect(checkNfoFileSpy).toHaveBeenCalledTimes(0);
expect(updateVideoImageSpy).toHaveBeenCalledTimes(0);

// Cleanup
watchFilesSpy.mockRestore();
enqueueFileProcessingJobSpy.mockRestore();
checkNfoFileSpy.mockRestore();
updateVideoImageSpy.mockRestore();
fileProcessingQueueSpy.mockRestore();
});

Expand All @@ -126,9 +128,6 @@ describe("WatchService", () => {
const checkNfoFileSpy = jest
.spyOn(service, "checkNfoFile")
.mockReturnValue(true);
const updateVideoImageSpy = jest
.spyOn(service, "updateVideoImage")
.mockImplementation(() => null);
const enqueueFileProcessingJobSpy = jest
.spyOn(service, "enqueueFileProcessingJob")
.mockResolvedValue(Promise.resolve());
Expand All @@ -144,7 +143,6 @@ describe("WatchService", () => {
watchFilesSpy.mockRestore();
enqueueFileProcessingJobSpy.mockRestore();
checkNfoFileSpy.mockRestore();
updateVideoImageSpy.mockRestore();
});
});

Expand Down Expand Up @@ -203,95 +201,6 @@ describe("WatchService", () => {
});
});

describe("getNfoImage", () => {
it("should return image paths if nfo file contains them", () => {
// Arrange
const filePath = "path/to/video.mp4";
const nfoPath = "path/to/video.nfo";
const mockNfoContent =
"<poster>poster.jpg</poster><fanart>fanart.jpg</fanart>";
(fs.readFileSync as jest.Mock).mockReturnValue(mockNfoContent);
(fs.existsSync as jest.Mock).mockReturnValue(true);

// Act
const result = service.getNfoImage(filePath);

// Assert
expect(fs.readFileSync).toHaveBeenCalledWith(nfoPath, "utf-8");
expect(fs.existsSync).toBeCalled(); // Or the exact number of times it should be called
expect(result).toEqual({
poster: "poster.jpg",
fanart: "fanart.jpg",
});
});

it("should return null if nfo file does not contain image paths", () => {
// Arrange
const filePath = "path/to/video.mp4";
const nfoPath = "path/to/video.nfo";
const mockNfoContent = "<noimage></noimage>";
(fs.readFileSync as jest.Mock).mockReturnValue(mockNfoContent);
// Act
const result = service.getNfoImage(filePath);

// Assert
expect(fs.readFileSync).toHaveBeenCalledWith(nfoPath, "utf-8");
expect(result).toEqual({
poster: null,
fanart: null,
});
});

// Add more test cases for different scenarios
});

describe("updateVideoImage", () => {
beforeEach(() => {
jest
.spyOn(service, "getNfoImage")
.mockReturnValue({ poster: "poster.jpg", fanart: "fanart.jpg" });
});

it("should update video image if file exists", async () => {
// Arrange
const filePath = "path/to/video.mp4";
const poster = "path/to/poster.jpg";
const fanart = "path/to/fanart.jpg";
jest.spyOn(service, "getNfoImage").mockReturnValue({ poster, fanart });

// Act
const result = await service.updateVideoImage(filePath);

// Assert
expect(service.getNfoImage).toHaveBeenCalledWith(filePath);
expect(mockVideoFileRepo.update).toHaveBeenCalledWith(
{ filePath },
{ poster, fanart }
);
});
});

describe("findAndClassifyFiles", () => {
it("should classify files correctly", async () => {
// Arrange
const fakeStream = [
"path/to/video.mp4",
"path/to/audio.mp3",
"path/to/subtitle.srt",
"path/to/other.xyz",
][Symbol.iterator]();
jest.spyOn(fg, "stream").mockReturnValue(fakeStream as any);
jest.spyOn(fg, "async").mockReturnValue(fakeStream as any);
jest.spyOn(path, "join").mockReturnValue("path/join/result");

// Act
const result = await service.findAndClassifyFilesWithDir();

// Assert
expect(result).toEqual([]);
});
});

describe("create", () => {
it("should create a new watch", async () => {
const createWatchDto: CreateWatchDto = {
Expand Down
Loading

0 comments on commit 04be5eb

Please sign in to comment.