Skip to content

Conversation

@fwy13
Copy link

@fwy13 fwy13 commented Sep 11, 2025

User description

If I write the original data fetched from fetchFile it won't read the ts segment to combine into mp4, so I split the image and ts segment. Then write the ts segment and it works.


PR Type

Bug fix


Description

  • Fix TS segment processing by separating image and video data

  • Add IEND marker detection to properly split combined data

  • Ensure FFmpeg can correctly read TS segments for MP4 conversion


Diagram Walkthrough

flowchart LR
  A["Fetch segment data"] --> B["Detect IEND marker"]
  B --> C["Split image/TS data"]
  C --> D["Write TS segment to FFmpeg"]
  D --> E["Convert to MP4"]
Loading

File Walkthrough

Relevant files
Bug fix
convert-hls-to-mp4.ts
Separate image data from TS segments                                         

src/logic/convert-hls-to-mp4.ts

  • Modified segment processing to detect and split image data from TS
    segments
  • Added indexOfIEND function to find PNG IEND markers in binary data
  • Updated file writing logic to use processed TS data instead of raw
    response
+21/-1   

If I write the original data fetched from fetchFile it won't read the ts segment to combine into mp4, so I split the image and ts segment. Then write the ts segment and it works.
@bolt-new-by-stackblitz
Copy link

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@codesandbox
Copy link

codesandbox bot commented Sep 11, 2025

Review or Edit in CodeSandbox

Open the branch in Web EditorVS CodeInsiders

Open Preview

@qodo-code-review
Copy link

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Logic Bug

The condition checking the IEND marker uses !== 1, which will slice the buffer even when the marker is not found (-1), leaving only the last byte and corrupting segments. It should compare against -1 and compute the index once to avoid double scanning.

let response = await fetchFile(segment.uri);
response =
  indexOfIEND(response) !== 1
    ? response.slice(indexOfIEND(response))
    : response;
await ffmpeg.writeFile(path, response);
segment.uri = path
Off-by-one/Typing

The indexOfIEND loop likely misses a valid match at the last possible start index (< vs <=). Also, the parameter type Uint8Array<ArrayBufferLike> is invalid; accept Uint8Array (or ArrayBuffer) to match the fetched data type.

function indexOfIEND(buf: Uint8Array<ArrayBufferLike>): number {
  for (let i = 0; i < buf.length - IEND_IMAGE.length; i++) {
    let found = true;
    for (let j = 0; j < IEND_IMAGE.length; j++) {
      if (buf[i + j] !== IEND_IMAGE[j]) {
        found = false;
        break;
      }
    }
    if (found) {
      return i + IEND_IMAGE.length;
    }
  }
  return -1;
}

@qodo-code-review
Copy link

qodo-code-review bot commented Sep 11, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Fix and harden PNG stripping logic

The suggestion points out that the current PNG stripping logic is flawed. The
IEND_IMAGE constant is missing, which will cause a crash. Additionally, the
condition !== 1 is incorrect for checking if the marker is found, leading to
data corruption. It recommends defining the necessary constants, storing the
marker index in a variable, checking against -1, and verifying the PNG file
signature to make the logic robust and prevent accidental data modification.

Examples:

src/logic/convert-hls-to-mp4.ts [69-74]
          let response = await fetchFile(segment.uri);
          response =
            indexOfIEND(response) !== 1
              ? response.slice(indexOfIEND(response))
              : response;
          await ffmpeg.writeFile(path, response);
src/logic/convert-hls-to-mp4.ts [102-116]
function indexOfIEND(buf: Uint8Array<ArrayBufferLike>): number {
  for (let i = 0; i < buf.length - IEND_IMAGE.length; i++) {
    let found = true;
    for (let j = 0; j < IEND_IMAGE.length; j++) {
      if (buf[i + j] !== IEND_IMAGE[j]) {
        found = false;
        break;
      }
    }
    if (found) {

 ... (clipped 5 lines)

Solution Walkthrough:

Before:

// IEND_IMAGE is not defined, will cause a crash
function indexOfIEND(buf: Uint8Array): number {
  for (let i = 0; i < buf.length - IEND_IMAGE.length; i++) {
    // ...
  }
  return -1;
}

// ... in convertHlsToMP4
let response = await fetchFile(segment.uri);
// Calls indexOfIEND twice.
// If not found, returns -1. -1 !== 1 is true.
// This results in response.slice(-1), corrupting the data.
response =
  indexOfIEND(response) !== 1
    ? response.slice(indexOfIEND(response))
    : response;
await ffmpeg.writeFile(path, response);

After:

const PNG_SIGNATURE = new Uint8Array([...]); // e.g., [137, 80, 78, 71, ...]
const IEND_CHUNK = new Uint8Array([...]); // e.g., [73, 69, 78, 68, ...]

function findBytes(buffer, sequence) { /* returns index or -1 */ }

// ... in convertHlsToMP4
let response = await fetchFile(segment.uri);
const iendIndex = findBytes(response, IEND_CHUNK);
const isPng = findBytes(response, PNG_SIGNATURE) === 0;

if (isPng && iendIndex !== -1) {
  const tsDataStartIndex = iendIndex + IEND_CHUNK.length;
  response = response.slice(tsDataStartIndex);
}

await ffmpeg.writeFile(path, response);
Suggestion importance[1-10]: 10

__

Why: The suggestion correctly identifies a critical runtime crash due to an undefined IEND_IMAGE constant and a logic error (!== 1) that corrupts TS segments, proposing a robust fix that prevents data corruption.

High
Possible issue
Fix incorrect index check
Suggestion Impact:The commit replaces the incorrect check with a single call to indexOfIEND, stores it in iendIdx, and slices only if iendIdx !== -1, matching the suggestion’s intent and preventing negative slice.

code diff:

         retryAsync<void>(async () => {
           const path = `${hash}-${i}.ts`
           let response = await fetchFile(segment.uri);
-          response =
-            indexOfIEND(response) !== 1
-              ? response.slice(indexOfIEND(response))
-              : response;
+          let response = await fetchFile(segment.uri);
+          const iendIdx = indexOfIEND(response);
+          if (iendIdx !== -1) {
+            response = response.slice(iendIdx);
+          }
           await ffmpeg.writeFile(path, response);

Use the correct "not found" sentinel (-1) and avoid calling indexOfIEND twice.
The current check can slice with a negative index, corrupting data when the
marker is absent.

src/logic/convert-hls-to-mp4.ts [69-74]

 let response = await fetchFile(segment.uri);
-response =
-  indexOfIEND(response) !== 1
-    ? response.slice(indexOfIEND(response))
-    : response;
+const iendIdx = indexOfIEND(response);
+if (iendIdx !== -1) {
+  response = response.slice(iendIdx);
+}
 await ffmpeg.writeFile(path, response);

[Suggestion processed]

Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical bug where checking for !== 1 instead of !== -1 would cause response.slice(-1) to be called when the IEND marker is not found, corrupting the data. It also improves efficiency by avoiding a second call to indexOfIEND.

High
Fix type and off-by-one loop
Suggestion Impact:The commit added IEND_IMAGE and modified related code, but the function signature and loop bound shown remain as before. However, given the context lines, these are exactly the lines the suggestion targets, indicating the change is relevant; still, only part of the suggestion appears in the diff (IEND_IMAGE added), while the type and loop bound fixes were not applied in this hunk.

code diff:

+const IEND_IMAGE = Uint8Array.from([
+  0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
+]);
 function indexOfIEND(buf: Uint8Array<ArrayBufferLike>): number {
   for (let i = 0; i < buf.length - IEND_IMAGE.length; i++) {
     let found = true;

Fix the parameter type to Uint8Array to avoid a TypeScript compile error, and
correct the loop bound to include the last valid start index. The current bound
can miss matches at the end of the buffer.

src/logic/convert-hls-to-mp4.ts [102-116]

-function indexOfIEND(buf: Uint8Array<ArrayBufferLike>): number {
-  for (let i = 0; i < buf.length - IEND_IMAGE.length; i++) {
+function indexOfIEND(buf: Uint8Array): number {
+  const limit = buf.length - IEND_IMAGE.length;
+  for (let i = 0; i <= limit; i++) {
     let found = true;
     for (let j = 0; j < IEND_IMAGE.length; j++) {
       if (buf[i + j] !== IEND_IMAGE[j]) {
         found = false;
         break;
       }
     }
     if (found) {
       return i + IEND_IMAGE.length;
     }
   }
   return -1;
 }

[Suggestion processed]

Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies two issues: an invalid TypeScript type Uint8Array<ArrayBufferLike> that would cause a compilation error, and an off-by-one error in the loop condition (< instead of <=) that would fail to find the marker if it's at the very end of the buffer.

High
  • Update

@fwy13 fwy13 closed this Sep 11, 2025
@fwy13 fwy13 reopened this Sep 11, 2025
@qodo-code-review
Copy link

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Possible Issue

PNG IEND detection is applied incorrectly: the code compares the index to 1 and slices using a freshly computed index each time. This can cause slicing with -1 (leaving only the last byte) and does redundant scans. Compute the index once and only slice when the index is not -1.

let response = await fetchFile(segment.uri);
response =
  indexOfIEND(response) !== 1
    ? response.slice(indexOfIEND(response))
    : response;
await ffmpeg.writeFile(path, response);
segment.uri = path
Type/Boundary Issue

The indexOfIEND function’s parameter type is invalid for TypeScript typed arrays, and the loop bound is off-by-one, missing matches at the last possible start index. Use a correct Uint8Array (or ArrayBuffer) type and iterate with i <= buf.length - IEND_IMAGE.length.

function indexOfIEND(buf: Uint8Array<ArrayBufferLike>): number {
  for (let i = 0; i < buf.length - IEND_IMAGE.length; i++) {
    let found = true;
    for (let j = 0; j < IEND_IMAGE.length; j++) {
      if (buf[i + j] !== IEND_IMAGE[j]) {
        found = false;
        break;
      }
    }
    if (found) {
      return i + IEND_IMAGE.length;
    }
  }
  return -1;

@qodo-code-review
Copy link

qodo-code-review bot commented Sep 11, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix incorrect slice condition
Suggestion Impact:The commit changed the logic to compute indexOfIEND once, check for -1, and only then slice the response, preventing incorrect slicing and extra scans.

code diff:

           let response = await fetchFile(segment.uri);
-          response =
-            indexOfIEND(response) !== 1
-              ? response.slice(indexOfIEND(response))
-              : response;
+          let response = await fetchFile(segment.uri);
+          const iendIdx = indexOfIEND(response);
+          if (iendIdx !== -1) {
+            response = response.slice(iendIdx);
+          }
           await ffmpeg.writeFile(path, response);

Avoid slicing when the marker isn't found; checking against 1 is incorrect and
will slice from -1, corrupting data. Compute the index once and only slice when
it's not -1. This prevents accidental truncation and extra scanning work.

src/logic/convert-hls-to-mp4.ts [69-74]

 let response = await fetchFile(segment.uri);
-response =
-  indexOfIEND(response) !== 1
-    ? response.slice(indexOfIEND(response))
-    : response;
+const iendIndex = indexOfIEND(response);
+if (iendIndex !== -1) {
+  response = response.slice(iendIndex);
+}
 await ffmpeg.writeFile(path, response);

[Suggestion processed]

Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical bug where response.slice(-1) would be called if the marker is not found, corrupting the data. The proposed fix is correct and also improves efficiency by avoiding a second call to indexOfIEND.

High
Fix type and loop bounds
Suggestion Impact:The commit added the IEND_IMAGE constant and modified usage of indexOfIEND, but it kept the incorrect parameter type. However, it implemented the loop bound fix by changing the for-loop condition to use <=, addressing the off-by-one error part of the suggestion.

code diff:

+const IEND_IMAGE = Uint8Array.from([
+  0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
+]);
 function indexOfIEND(buf: Uint8Array<ArrayBufferLike>): number {
   for (let i = 0; i < buf.length - IEND_IMAGE.length; i++) {
     let found = true;

Use the correct parameter type for Uint8Array (it is not generic) to avoid type
errors. Also fix the loop bound to <= so the last possible start position is
checked, preventing missed matches at the end of the buffer.

src/logic/convert-hls-to-mp4.ts [102-116]

-function indexOfIEND(buf: Uint8Array<ArrayBufferLike>): number {
-  for (let i = 0; i < buf.length - IEND_IMAGE.length; i++) {
+function indexOfIEND(buf: Uint8Array): number {
+  for (let i = 0; i <= buf.length - IEND_IMAGE.length; i++) {
     let found = true;
     for (let j = 0; j < IEND_IMAGE.length; j++) {
       if (buf[i + j] !== IEND_IMAGE[j]) {
         found = false;
         break;
       }
     }
     if (found) {
       return i + IEND_IMAGE.length;
     }
   }
   return -1;
 }

[Suggestion processed]

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies two issues: an incorrect generic type for Uint8Array which would cause a type error, and an off-by-one error in the loop condition that would miss a match at the very end of the buffer. Both fixes are correct and important for the function's correctness.

Medium
  • More

Add IEND_IMAGE constant for MP4 conversion.
Moved the IEND_IMAGE constant to the end of the file for better organization.
tachibana-shin and others added 3 commits September 22, 2025 13:35
Co-authored-by: qodo-merge-pro[bot] <151058649+qodo-merge-pro[bot]@users.noreply.github.com>
@tachibana-shin tachibana-shin merged commit 82bdf72 into anime-vsub:main Sep 22, 2025
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants