|
@@ -1,6 +1,6 @@
|
|
|
import { Injectable, Logger } from '@nestjs/common';
|
|
import { Injectable, Logger } from '@nestjs/common';
|
|
|
import { spawn } from 'child_process';
|
|
import { spawn } from 'child_process';
|
|
|
-import { mkdirSync } from 'fs';
|
|
|
|
|
|
|
+import { existsSync, mkdirSync, readdirSync } from 'fs';
|
|
|
import path from 'path';
|
|
import path from 'path';
|
|
|
import { DbService } from './db.service';
|
|
import { DbService } from './db.service';
|
|
|
import { EventsGateway } from './events.gateway';
|
|
import { EventsGateway } from './events.gateway';
|
|
@@ -14,6 +14,82 @@ export class HandbrakeService {
|
|
|
private readonly db: DbService,
|
|
private readonly db: DbService,
|
|
|
) {}
|
|
) {}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Find existing directory with case-insensitive matching
|
|
|
|
|
+ * Returns the actual path if found, null otherwise
|
|
|
|
|
+ */
|
|
|
|
|
+ private findExistingDirCaseInsensitive(dirPath: string): string | null {
|
|
|
|
|
+ const parentDir = path.dirname(dirPath);
|
|
|
|
|
+ const targetName = path.basename(dirPath);
|
|
|
|
|
+
|
|
|
|
|
+ // If parent doesn't exist, can't find anything
|
|
|
|
|
+ if (!existsSync(parentDir)) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const entries = readdirSync(parentDir, { withFileTypes: true });
|
|
|
|
|
+ const match = entries.find(
|
|
|
|
|
+ (entry) =>
|
|
|
|
|
+ entry.isDirectory() &&
|
|
|
|
|
+ entry.name.toLowerCase() === targetName.toLowerCase(),
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ return match ? path.join(parentDir, match.name) : null;
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ this.logger.warn(
|
|
|
|
|
+ `Error checking for case-insensitive directory: ${dirPath}`,
|
|
|
|
|
+ err,
|
|
|
|
|
+ );
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Ensure output directory exists, handling case-insensitive matches
|
|
|
|
|
+ * Returns the actual directory path to use
|
|
|
|
|
+ */
|
|
|
|
|
+ private ensureOutputDirectory(outputPath: string): string {
|
|
|
|
|
+ const parts = outputPath.split(path.sep);
|
|
|
|
|
+ let currentPath = '';
|
|
|
|
|
+
|
|
|
|
|
+ // Build path incrementally, checking for case-insensitive matches
|
|
|
|
|
+ for (let i = 0; i < parts.length; i++) {
|
|
|
|
|
+ const part = parts[i];
|
|
|
|
|
+ if (!part) continue; // Skip empty parts (e.g., leading slash)
|
|
|
|
|
+
|
|
|
|
|
+ const proposedPath = currentPath ? path.join(currentPath, part) : part;
|
|
|
|
|
+
|
|
|
|
|
+ // Check if directory exists (exact case)
|
|
|
|
|
+ if (existsSync(proposedPath)) {
|
|
|
|
|
+ currentPath = proposedPath;
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Check for case-insensitive match
|
|
|
|
|
+ const existingPath = this.findExistingDirCaseInsensitive(proposedPath);
|
|
|
|
|
+ if (existingPath) {
|
|
|
|
|
+ this.logger.log(
|
|
|
|
|
+ `Found existing directory with different case: ${existingPath} (wanted: ${proposedPath})`,
|
|
|
|
|
+ );
|
|
|
|
|
+ currentPath = existingPath;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // Directory doesn't exist, create it
|
|
|
|
|
+ try {
|
|
|
|
|
+ mkdirSync(proposedPath, { recursive: false });
|
|
|
|
|
+ this.logger.log(`Created directory: ${proposedPath}`);
|
|
|
|
|
+ currentPath = proposedPath;
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ throw new Error(
|
|
|
|
|
+ `Failed to create directory ${proposedPath}: ${err.message}`,
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return currentPath;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
processWithHandbrake(
|
|
processWithHandbrake(
|
|
|
input: string,
|
|
input: string,
|
|
|
output: string,
|
|
output: string,
|
|
@@ -22,23 +98,31 @@ export class HandbrakeService {
|
|
|
): Promise<boolean> {
|
|
): Promise<boolean> {
|
|
|
return new Promise((resolve, reject) => {
|
|
return new Promise((resolve, reject) => {
|
|
|
try {
|
|
try {
|
|
|
- // Ensure output directory exists
|
|
|
|
|
|
|
+ // Ensure output directory exists, handling case-insensitive matches
|
|
|
const outputDir = path.dirname(output);
|
|
const outputDir = path.dirname(output);
|
|
|
|
|
+ let actualOutputDir: string;
|
|
|
|
|
+
|
|
|
try {
|
|
try {
|
|
|
- mkdirSync(outputDir, { recursive: true });
|
|
|
|
|
- this.logger.log(`Ensured output directory exists: ${outputDir}`);
|
|
|
|
|
|
|
+ actualOutputDir = this.ensureOutputDirectory(outputDir);
|
|
|
|
|
+ this.logger.log(
|
|
|
|
|
+ `Output directory ready: ${actualOutputDir}`,
|
|
|
|
|
+ );
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
this.logger.error(
|
|
this.logger.error(
|
|
|
- `Failed to create output directory: ${outputDir}`,
|
|
|
|
|
|
|
+ `Failed to prepare output directory: ${outputDir}`,
|
|
|
err,
|
|
err,
|
|
|
);
|
|
);
|
|
|
- return reject(
|
|
|
|
|
- new Error(`Cannot create output directory: ${outputDir}`),
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ return reject(err);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // Update output path to use actual directory (in case of case mismatch)
|
|
|
|
|
+ const actualOutput = path.join(actualOutputDir, path.basename(output));
|
|
|
|
|
+
|
|
|
|
|
+ // Update output path to use actual directory (in case of case mismatch)
|
|
|
|
|
+ const actualOutput = path.join(actualOutputDir, path.basename(output));
|
|
|
|
|
+
|
|
|
const inputName = path.basename(input);
|
|
const inputName = path.basename(input);
|
|
|
- const outputName = path.basename(output);
|
|
|
|
|
|
|
+ const outputName = path.basename(actualOutput);
|
|
|
let progressStarted = false;
|
|
let progressStarted = false;
|
|
|
let lastPercent = 0;
|
|
let lastPercent = 0;
|
|
|
|
|
|
|
@@ -46,7 +130,7 @@ export class HandbrakeService {
|
|
|
'-i',
|
|
'-i',
|
|
|
input,
|
|
input,
|
|
|
'-o',
|
|
'-o',
|
|
|
- output,
|
|
|
|
|
|
|
+ actualOutput,
|
|
|
'--preset',
|
|
'--preset',
|
|
|
preset,
|
|
preset,
|
|
|
]);
|
|
]);
|