diff --git a/fix_ts.js b/fix_ts.js new file mode 100644 index 0000000..4dc145e --- /dev/null +++ b/fix_ts.js @@ -0,0 +1,20 @@ +const fs = require('fs'); + +// Fix route.ts +let route = fs.readFileSync('src/app/api/media/[...path]/route.ts', 'utf8'); +route = route.replace( + "fileStream.on('data', (chunk) => controller.enqueue(new Uint8Array(chunk)));", + "fileStream.on('data', (chunk) => controller.enqueue(new Uint8Array(chunk as Buffer)));" +); +fs.writeFileSync('src/app/api/media/[...path]/route.ts', route, 'utf8'); + +// Fix MediaTile.tsx +let tile = fs.readFileSync('src/components/MediaTile.tsx', 'utf8'); +tile = tile.replace( + "const target = e.target;", + "const target = e.target as HTMLVideoElement;" +).replace( + "const target = e.target;", + "const target = e.target as HTMLImageElement;" +); +fs.writeFileSync('src/components/MediaTile.tsx', tile, 'utf8'); diff --git a/patch_range.js b/patch_range.js new file mode 100644 index 0000000..a933a38 --- /dev/null +++ b/patch_range.js @@ -0,0 +1,73 @@ +const fs = require('fs'); + +let route = fs.readFileSync('src/app/api/media/[...path]/route.ts', 'utf8'); + +const rangeLogic = ` + // Get file stats for Content-Length + const stat = await fs.promises.stat(filePath); + const fileSize = stat.size; + + // Handle Range Requests (CRITICAL for video streaming in Safari/Chrome) + const rangeHeader = request.headers.get('range'); + let start = 0; + let end = fileSize - 1; + let isPartial = false; + + if (rangeHeader) { + const parts = rangeHeader.replace(/bytes=/, '').split('-'); + const partialStart = parts[0]; + const partialEnd = parts[1]; + + start = parseInt(partialStart, 10); + end = partialEnd ? parseInt(partialEnd, 10) : fileSize - 1; + + if (start >= fileSize) { + return new NextResponse('Requested range not satisfiable', { status: 416 }); + } + isPartial = true; + } + + const contentLength = (end - start) + 1; + + // Build response headers + const contentType = getSafeContentType(contentTypeExt); + const headers = new Headers(); + headers.set('Content-Type', contentType); + headers.set('Content-Length', contentLength.toString()); + headers.set('Cache-Control', 'public, max-age=3600, stale-while-revalidate=86400'); + headers.set('Accept-Ranges', 'bytes'); + + if (isPartial) { + headers.set('Content-Range', \`bytes \${start}-\${end}/\${fileSize}\`); + } + + // SVG: force download to prevent XSS + if (contentType === 'image/svg+xml') { + headers.set('Content-Disposition', \`attachment; filename="\${path.basename(relativePath)}"\`); + } + + // Stream the file chunk + const fileStream = fs.createReadStream(filePath, { start, end }); + const webStream = new ReadableStream({ + start(controller) { + fileStream.on('data', (chunk) => controller.enqueue(new Uint8Array(chunk))); + fileStream.on('end', () => controller.close()); + fileStream.on('error', (err) => controller.error(err)); + }, + cancel() { + fileStream.destroy(); + }, + }); + + return new NextResponse(webStream, { + status: isPartial ? 206 : 200, + headers + }); +`; + +route = route.replace( + /\/\/ Get file stats for Content-Length[\s\S]+return new NextResponse\(webStream, \{ headers \}\);/, + rangeLogic.trim() +); + +fs.writeFileSync('src/app/api/media/[...path]/route.ts', route, 'utf8'); diff --git a/patch_tile.js b/patch_tile.js new file mode 100644 index 0000000..089eebd --- /dev/null +++ b/patch_tile.js @@ -0,0 +1,50 @@ +const fs = require('fs'); +let tile = fs.readFileSync('src/components/MediaTile.tsx', 'utf8'); + +const oldRender = `{isVisible && ( + <> + {/* eslint-disable-next-line @next/next/no-img-element */} + {item.filename} { + const target = e.target as HTMLImageElement; + target.parentElement?.classList.remove('loading-skeleton'); + }} + /> + {isVideo && ( +
`; + +const newRender = `{isVisible && ( + <> + {isVideo ? ( +