calculate_file_hash
Generate OpenSubtitles hash values from local movie files to enable subtitle matching and downloading through the OpenSubtitles API.
Instructions
Calculate OpenSubtitles hash for local movie files
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| file_path | Yes | Path to the movie file |
Implementation Reference
- dist/tools/calculate-file-hash.js:8-66 (handler)Main handler function implementing the calculate_file_hash tool logic: validates input, checks file accessibility, computes hash using helper, formats response, and handles errors.
export async function calculateFileHash(args) { try { // Validate input arguments const validatedArgs = HashArgsSchema.parse(args); // Resolve and validate file path const resolvedPath = resolve(validatedArgs.file_path); // Check if file exists and is readable try { await access(resolvedPath, constants.R_OK); } catch (error) { throw new Error(`File not found or not readable: ${validatedArgs.file_path}`); } // Calculate the OpenSubtitles hash const hashResult = await calculateOpenSubtitlesHash(resolvedPath); // Format response const response = { file_path: validatedArgs.file_path, resolved_path: resolvedPath, hash: hashResult.hash, size: hashResult.size, size_mb: Math.round(hashResult.size / 1024 / 1024 * 100) / 100, usage_note: "Use this hash with the 'moviehash' parameter in search_subtitles for exact file matching" }; return { content: [ { type: "text", text: JSON.stringify(response, null, 2), }, ], }; } catch (error) { console.error("Calculate file hash error:", error); let errorMessage = "Failed to calculate file hash"; if (error instanceof z.ZodError) { errorMessage = `Invalid parameters: ${error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`; } else if (error instanceof Error) { errorMessage = error.message; } // Provide helpful error messages if (errorMessage.includes("not found")) { errorMessage += "\n\nMake sure the file path is correct and the file exists."; } else if (errorMessage.includes("too small")) { errorMessage += "\n\nThe file must be at least 128KB for hash calculation."; } return { content: [ { type: "text", text: `Error: ${errorMessage}`, }, ], }; } } - dist/server.js:149-159 (schema)MCP input schema definition for the calculate_file_hash tool, specifying required file_path parameter.
inputSchema: { type: "object", properties: { file_path: { type: "string", description: "Path to the movie file" } }, required: ["file_path"], additionalProperties: false } - dist/server.js:146-160 (registration)Tool registration in the MCP server's tools array, including name, description, and input schema.
{ name: "calculate_file_hash", description: "Calculate OpenSubtitles hash for local movie files", inputSchema: { type: "object", properties: { file_path: { type: "string", description: "Path to the movie file" } }, required: ["file_path"], additionalProperties: false } } - dist/utils/hash-calculator.js:8-44 (helper)Core helper utility that computes the OpenSubtitles-specific hash algorithm using file size and first/last 64KB chunks.
export async function calculateOpenSubtitlesHash(filePath) { try { // Get file size const stats = await stat(filePath); const fileSize = stats.size; if (fileSize < 131072) { // 128KB minimum throw new Error("File too small for OpenSubtitles hash calculation (minimum 128KB)"); } const chunkSize = 65536; // 64KB // Read first 64KB const firstChunk = await readChunk(filePath, 0, chunkSize); // Read last 64KB const lastChunk = await readChunk(filePath, fileSize - chunkSize, chunkSize); // Calculate hash using file size as initial value let hash = BigInt(fileSize); // Add values from first chunk for (let i = 0; i < firstChunk.length; i += 8) { hash += BigInt(firstChunk.readBigUInt64LE(i)); } // Add values from last chunk for (let i = 0; i < lastChunk.length; i += 8) { hash += BigInt(lastChunk.readBigUInt64LE(i)); } // Convert to 16-character hex string const hashHex = (hash & BigInt("0xFFFFFFFFFFFFFFFF")).toString(16).padStart(16, '0'); return { hash: hashHex, size: fileSize, }; } catch (error) { if (error instanceof Error) { throw new Error(`Failed to calculate hash: ${error.message}`); } throw new Error("Failed to calculate hash: Unknown error"); } }