Cannot read file after rendering (ffmpeg)

I have a docker-compose.yml file as follows:

services:
postgres:
image: postgres:latest
container_name: postgres-demo-zenpr-net
restart: unless-stopped
environment:

  • POSTGRES_USER=${POSTGRES_USER}
  • POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
  • POSTGRES_DB=${POSTGRES_DB}
    volumes:
  • ./postgres_data:/var/lib/postgresql/data
    ports:
  • “5456:5432”
    n8n:
    build:
    context: .
    dockerfile: Dockerfile
    container_name: demo-zenpr-net
    restart: unless-stopped
    ports:
  • “3456:5678”
    depends_on:
  • postgres
    environment:

Variables taken from .env file (or system environment)

  • N8N_HOST=${SUBDOMAIN}.${DOMAIN_NAME}
  • WEBHOOK_URL=https://${SUBDOMAIN}.${DOMAIN_NAME}/

Specify DB type + Postgres connection information

  • DB_TYPE=postgresdb
  • DB_POSTGRESDB_HOST=postgres
  • DB_POSTGRESDB_PORT=5432
  • DB_POSTGRESDB_DATABASE=${POSTGRES_DB}
  • DB_POSTGRESDB_USER=${POSTGRES_USER}
  • DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}

Enable saving files to hard drive instead of embedding in DB

  • N8N_DEFAULT_BINARY_DATA_MODE=${N8N_DEFAULT_BINARY_DATA_MODE}

Tighten config file permissions (or disable warnings)

  • N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=${N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS}

Timezone

  • GENERIC_TIMEZONE=${GENERIC_TIMEZONE}

Set cleanup of executions history

EXECUTIONS_DATA_PRUNE=${EXECUTIONS_DATA_PRUNE}

EXECUTIONS_DATA_MAX_AGE=${EXECUTIONS_DATA_MAX_AGE}

EXECUTIONS_DATA_PRUNE_MAX_COUNT=${EXECUTIONS_DATA_PRUNE_MAX_COUNT}

Basic auth

  • N8N_BASIC_AUTH_ACTIVE=true

  • N8N_BASIC_AUTH_USER=${N8N_BASIC_AUTH_USER}

  • N8N_BASIC_AUTH_PASSWORD=${N8N_BASIC_AUTH_PASSWORD}
    volumes:

  • ./n8n_data:/home/node/.n8n

The .env file is as follows:

#===== Domain name information =====#
DOMAIN_NAME=zenpr.net
SUBDOMAIN=demo

When starting, n8n will listen at https://demo.zenpr.net

#===== Postgres information =====#
POSTGRES_USER=
POSTGRES_PASSWORD=
POSTGRES_DB=

General time zone configuration

GENERIC_TIMEZONE=Asia/Ho_Chi_Minh

#===== Store binary files (attachments…) on hard drive instead of DB =====#
N8N_DEFAULT_BINARY_DATA_MODE=filesystem

#===== config file permissions =====#
N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true

#===== General time zone =====#
GENERIC_TIMEZONE=Asia/Ho_Chi_Minh

#===== Clean up old logs/executions =====#
EXECUTIONS_DATA_PRUNE=true
EXECUTIONS_DATA_MAX_AGE=168 # (hours)
EXECUTIONS_DATA_PRUNE_MAX_COUNT=50000

#===== Basic Auth for n8n =====#
N8N_BASIC_AUTH_USER=Admin
N8N_BASIC_AUTH_PASSWORD=

And the Dockerfile is as follows:

FROM n8nio/n8n:latest

USER root

RUN apk update && apk add ffmpeg

USER node

Describe the problem/error/question

My workflow does the following:

List and sort files in the specified google drive folder, then download and use ffmeg command to render all video sources including clip, audio, img, srt into 1 single video.

Everything works fine and the output.mp4 file is in the n8n_data folder.

The problem is I don’t know how to read that file on n8n. I tried many ways, Node Read/Write Files from Disk, Node Code and use the following code:

// Get base64 string from node stdout Execute Command
const base64Data = items[0].json.stdout;

return [
{
json: { message: "File converted to binary successfully" },
binary: {
data: {
data: base64Data,
mimeType: "video/mp4",
fileName: "output.mp4",
}
}
}
];

still cannot get or read the output.mp4 file
## What is the error message (if any)?

## Please share your workflow

{
  "nodes": [
    {
      "parameters": {
        "jsCode": "// Lấy chuỗi base64 từ stdout của node Execute Command\nconst base64Data = items[0].json.stdout;\n\nreturn [\n  {\n    json: { message: \"File chuyển sang binary thành công\" },\n    binary: {\n      data: {\n        data: base64Data,\n        mimeType: \"video/mp4\",\n        fileName: \"output.mp4\",\n      }\n    }\n  }\n];\n"
      },
      "name": "Set Binary Data1",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -580,
        660
      ],
      "id": "daa98ac1-bc1e-43f6-af70-22fa19c0d3e1"
    },
    {
      "parameters": {
        "resource": "fileFolder",
        "searchMethod": "=",
        "filter": {
          "folderId": {
            "__rl": true,
            "value": "1zgW6grvT_RJvUJLP96QX3l0WUPitgyAj",
            "mode": "list",
            "cachedResultName": "Animated Faceless",
            "cachedResultUrl": "https://drive.google.com/drive/folders/1zgW6grvT_RJvUJLP96QX3l0WUPitgyAj"
          }
        },
        "options": {
          "fields": [
            "id",
            "name"
          ]
        }
      },
      "name": "Google Drive Get All File3",
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 3,
      "position": [
        -1860,
        580
      ],
      "id": "a0ee40e0-1eb2-4f4d-bf7b-6b081c22f4e2",
      "credentials": {
        "googleDriveOAuth2Api": {
          "id": "71gEopHUimnQ9srI",
          "name": "Google Drive account layekaloustian15"
        }
      }
    },
    {
      "parameters": {
        "aggregate": "aggregateAllItemData",
        "destinationFieldName": "list",
        "options": {}
      },
      "name": "Aggregate Files2",
      "type": "n8n-nodes-base.aggregate",
      "typeVersion": 1,
      "position": [
        -1640,
        580
      ],
      "id": "aa8b3e23-f096-4b7b-a6af-2be4b0befda1"
    },
    {
      "parameters": {
        "jsCode": "// Phân loại file\nconst aggregatedList = items[0].json.list || [];\n\nlet images = [];\nlet videos = [];\nlet audios = [];\nlet subtitles = [];\n\naggregatedList.forEach(file => {\n  const url = `https://drive.google.com/uc?id=${file.id}&export=download`;\n  const extension = file.name.split('.').pop().toLowerCase();\n\n  if ([\"jpg\", \"jpeg\", \"png\", \"gif\"].includes(extension)) {\n    images.push({ name: file.name, url });\n  } else if ([\"mp4\", \"mov\", \"avi\"].includes(extension)) {\n    videos.push({ name: file.name, url });\n  } else if ([\"mp3\", \"aac\", \"wav\"].includes(extension)) {\n    audios.push({ name: file.name, url });\n  } else if (extension === \"srt\") {\n    subtitles.push({ name: file.name, url });\n  }\n});\n\nreturn [{ images, videos, audios, subtitles }];"
      },
      "name": "Process Files by Type2",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1440,
        580
      ],
      "id": "0243406f-304a-4282-b238-2e27cdd19ab7"
    },
    {
      "parameters": {
        "jsCode": "// Đường dẫn làm việc (được mount volume)\nconst workDir = '/home/node/.n8n';\n\n// Lấy danh sách file từ input (mỗi mảng có thể rỗng nếu không có file nào)\nconst { images = [], videos = [], audios = [], subtitles = [] } = items[0].json;\n\n// Mảng chứa các lệnh cần thực thi tuần tự\nlet arrCommands = [];\n\n// ----------------------------------------------------------------\n// 1. TẢI CÁC FILE VỀ (video, audio, image, subtitle)\n// ----------------------------------------------------------------\nvideos.forEach((file, i) => {\n  arrCommands.push(`wget \"${file.url}\" -O ${workDir}/video${i}.mp4`);\n});\n\naudios.forEach((file, i) => {\n  arrCommands.push(`wget \"${file.url}\" -O ${workDir}/audio${i}.mp3`);\n});\n\nimages.forEach((file, i) => {\n  arrCommands.push(`wget \"${file.url}\" -O ${workDir}/image${i}.jpg`);\n});\n\nsubtitles.forEach((file, i) => {\n  arrCommands.push(`wget \"${file.url}\" -O ${workDir}/subtitle${i}.srt`);\n});\n\n// ----------------------------------------------------------------\n// 2. CHUYỂN ẢNH THÀNH VIDEO NGẮN (nếu có ảnh)\n//    Mỗi ảnh sẽ được chuyển thành một đoạn video có thời lượng 5 giây\n// ----------------------------------------------------------------\nif (images.length > 0) {\n  images.forEach((file, i) => {\n    // Tạo video từ ảnh với thời lượng 5 giây\n    arrCommands.push(`ffmpeg -loop 1 -t 5 -i ${workDir}/image${i}.jpg -c:v libx264 -pix_fmt yuv420p -y ${workDir}/imgclip${i}.mp4`);\n  });\n}\n\n// ----------------------------------------------------------------\n// 3. XÂY DỰNG FILE DANH SÁCH (filelist.txt) để nối video\n//\n//    - Với các clip gốc: video0.mp4, video1.mp4, …\n//    - Với các đoạn video được tạo từ ảnh: imgclip0.mp4, imgclip1.mp4, …\n//\n//    Ở đây thứ tự được đặt là: TẤT CẢ các video gốc trước, sau đó là các video từ ảnh.\n//    (Bạn có thể thay đổi thứ tự theo yêu cầu của mình)\n// ----------------------------------------------------------------\nlet fileList = \"\";\nvideos.forEach((file, i) => {\n  fileList += `file '${workDir}/video${i}.mp4'\\\\n`;\n});\nif (images.length > 0) {\n  images.forEach((file, i) => {\n    fileList += `file '${workDir}/imgclip${i}.mp4'\\\\n`;\n  });\n}\n\n// Sử dụng printf để ghi filelist.txt với ký tự xuống dòng được xử lý đúng\narrCommands.push(`printf \"%b\" \"${fileList}\" > ${workDir}/filelist.txt`);\n\n// ----------------------------------------------------------------\n// 4. NỐI (CONCAT) CÁC ĐOẠN VIDEO lại với nhau thành combined.mp4\n// ----------------------------------------------------------------\narrCommands.push(`ffmpeg -f concat -safe 0 -i ${workDir}/filelist.txt -c copy ${workDir}/combined.mp4`);\n\n// ----------------------------------------------------------------\n// 5. GHÉP AUDIO (nếu có)\n//    Giả sử dùng audio0.mp3 làm nhạc nền cho toàn bộ video\n// ----------------------------------------------------------------\nif (audios.length > 0) {\n  arrCommands.push(`ffmpeg -i ${workDir}/combined.mp4 -i ${workDir}/audio0.mp3 -c:v copy -c:a aac -shortest ${workDir}/combined_audio.mp4`);\n}\n\n// ----------------------------------------------------------------\n// 6. ÁP DỤNG PHỤ ĐỀ (nếu có)\n//    Sử dụng file subtitle0.srt cho toàn bộ video.\n//    Chọn đầu vào là video đã ghép audio nếu có, ngược lại dùng combined.mp4\n// ----------------------------------------------------------------\nif (subtitles.length > 0) {\n  if (audios.length > 0) {\n    arrCommands.push(`ffmpeg -i ${workDir}/combined_audio.mp4 -vf \"subtitles=${workDir}/subtitle0.srt\" -c:v libx264 -c:a copy ${workDir}/output.mp4`);\n  } else {\n    arrCommands.push(`ffmpeg -i ${workDir}/combined.mp4 -vf \"subtitles=${workDir}/subtitle0.srt\" -c:v libx264 -c:a copy ${workDir}/output.mp4`);\n  }\n} else {\n  // Nếu không có phụ đề, chỉ cần sử dụng video đã ghép audio (nếu có) hoặc video gốc\n  if (audios.length > 0) {\n    arrCommands.push(`cp ${workDir}/combined_audio.mp4 ${workDir}/output.mp4`);\n  } else {\n    arrCommands.push(`cp ${workDir}/combined.mp4 ${workDir}/output.mp4`);\n  }\n}\n\n// ----------------------------------------------------------------\n// 7. Ghép tất cả các lệnh lại với nhau, sử dụng '&&' để đảm bảo chạy tuần tự\n// ----------------------------------------------------------------\nconst finalCmd = arrCommands.join(' && ');\n\nreturn [{ command: finalCmd }];\n"
      },
      "name": "Build FFmpeg Command3",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1240,
        580
      ],
      "id": "789e9fd4-b644-4d56-858f-d98a1cbac991"
    },
    {
      "parameters": {
        "command": "={{ $json[\"command\"] }}"
      },
      "name": "Execute FFmpeg Command3",
      "type": "n8n-nodes-base.executeCommand",
      "typeVersion": 1,
      "position": [
        -1040,
        580
      ],
      "id": "de27e5ce-c037-4163-86cd-c77eee204a34"
    },
    {
      "parameters": {
        "fileSelector": "/home/node/.n8n/output.mp4",
        "options": {}
      },
      "type": "n8n-nodes-base.readWriteFile",
      "typeVersion": 1,
      "position": [
        -780,
        520
      ],
      "id": "39e759a7-c80d-48d3-b5b9-87fc5aebe22a",
      "name": "Read/Write Files from Disk"
    },
    {
      "parameters": {
        "command": "base64 /home/node/.n8n/output.mp4"
      },
      "type": "n8n-nodes-base.executeCommand",
      "typeVersion": 1,
      "position": [
        -780,
        800
      ],
      "id": "f62a3559-f7f6-4952-a897-fa6daaa59a6e",
      "name": "Execute Command"
    }
  ],
  "connections": {
    "Set Binary Data1": {
      "main": [
        []
      ]
    },
    "Google Drive Get All File3": {
      "main": [
        [
          {
            "node": "Aggregate Files2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate Files2": {
      "main": [
        [
          {
            "node": "Process Files by Type2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Files by Type2": {
      "main": [
        [
          {
            "node": "Build FFmpeg Command3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build FFmpeg Command3": {
      "main": [
        [
          {
            "node": "Execute FFmpeg Command3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Execute FFmpeg Command3": {
      "main": [
        [
          {
            "node": "Execute Command",
            "type": "main",
            "index": 0
          },
          {
            "node": "Read/Write Files from Disk",
            "type": "main",
            "index": 0
          },
          {
            "node": "Set Binary Data1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": true,
    "instanceId": "250522b2a43a126dffad96a0033609652bd7a63892ddcb1c067e3e93979795cc"
  }
}

Share the output returned by the last node

Information on your n8n setup

  • **n8n version:1.77.3
  • **Database (default: SQLite): image: postgres:latest
  • n8n EXECUTIONS_PROCESS setting (default: own, main):
  • **Running n8n via Docker Compose
  • **Operating system:Ubuntu 24.04 4CPU 8GB Ram

It looks like your topic is missing some important information. Could you provide the following if applicable.

  • n8n version:
  • Database (default: SQLite):
  • n8n EXECUTIONS_PROCESS setting (default: own, main):
  • Running n8n via (Docker, npm, n8n cloud, desktop app):
  • Operating system:

By default, read access is blocked to the /home/node/.n8n path within the container.

Option 1

Mount a second path/volume in your Docker container for workflow data files.

  volumes:
    - ./n8n_data:/home/node/.n8n
    # add the following line to docker-compose.yml
    - ./workflow_scratch:/tmp/workflow_scratch

And change your workflow nodes to write/read files to/from /tmp/workflow_scratch

Option 2 (NOT recommended)

Add the N8N_BLOCK_FILE_ACCESS_TO_N8N_FILES environment variable to the environment section of your Docker compose, and set it to false (default is true. i.e. block access).
See docs here

If this answers your question and/or resolves your issue, please mark this post/reply as the Solution

1 Like