This workflow follows the Chat Trigger → Google Sheets recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"createdAt": "2025-06-06T20:25:30.273Z",
"updatedAt": "2025-06-12T00:26:49.000Z",
"id": "YPjY5Fm5h3SpijZI",
"name": "Tiktok",
"active": false,
"isArchived": false,
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "tiktok-transform",
"options": {}
},
"id": "db72418c-59b4-437b-9dd5-1aa9f4ee27ed",
"name": "Webhook TikTok",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [
-1160,
-180
]
},
{
"parameters": {
"functionCode": "// Validation et extraction de l'URL TikTok\nconst input = $input.first().json;\nconst tiktokUrl = input.url || input.tiktok_url || input.chatInput;\n\nif (!tiktokUrl) {\n throw new Error('URL TikTok manquante');\n}\n\n// V\u00e9rification format URL TikTok\nif (!tiktokUrl.includes('tiktok.com')) {\n throw new Error('URL TikTok invalide');\n}\n\nreturn {\n json: {\n tiktok_url: tiktokUrl,\n timestamp: new Date().toISOString(),\n workflow_id: `workflow_${Date.now()}`\n }\n};"
},
"id": "853bb7f2-025a-424c-af3f-1d2dcadf843f",
"name": "Valider URL",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
-900,
-100
]
},
{
"parameters": {
"url": "https://tikwm.com/api/",
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "url",
"value": "={{ $json.tiktok_url }}"
},
{
"name": "hd",
"value": "1"
}
]
},
"options": {}
},
"id": "95353939-a2b1-45f2-97aa-244abf5d08af",
"name": "T\u00e9l\u00e9charger TikTok",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [
-660,
-100
]
},
{
"parameters": {
"command": "=wget -O /tmp/video_{{ $json.workflow_id }}.mp4 \"{{ $json.video_url }}\""
},
"id": "ef3939a1-6244-42fa-a701-cf5220081a64",
"name": "Sauvegarder Vid\u00e9o",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
-220,
-100
],
"alwaysOutputData": true,
"notesInFlow": false
},
{
"parameters": {
"command": "=ffmpeg -i /tmp/video_{{ $(\"Edit Download\").first().json.workflow_id }}.mp4 -vn -acodec pcm_s16le /tmp/audio_{{ $(\"Edit Download\").first().json.workflow_id }}.wav"
},
"id": "6f98d8a7-6fa7-4024-a121-e3b208b2a0c7",
"name": "Extraire Audio",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
0,
-180
]
},
{
"parameters": {
"command": "=mkdir -p /tmp/frames_{{ $(\"Edit Download\").first().json.workflow_id }};\nffmpeg -i /tmp/video_{{ $(\"Edit Download\").first().json.workflow_id }}.mp4 -vf fps=2 /tmp/frames_{{ $(\"Edit Download\").first().json.workflow_id }}/frame_%04d.png"
},
"id": "c42f61a5-2ebb-4b07-80a2-01174f1e3837",
"name": "Extraire Frames",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
0,
-20
]
},
{
"parameters": {
"functionCode": "// Script Python pour d\u00e9tecter les visages avec MediaPipe\nconst pythonScript = `\nimport cv2\nimport mediapipe as mp\nimport json\nimport glob\nimport os\n\nmp_face_detection = mp.solutions.face_detection\nmp_drawing = mp.solutions.drawing_utils\n\nworkflow_id = \"${$json.workflow_id}\"\nframes_path = f\"/tmp/frames_{workflow_id}/*.png\"\nresults = []\n\nwith mp_face_detection.FaceDetection(model_selection=0, min_detection_confidence=0.5) as face_detection:\n frame_files = sorted(glob.glob(frames_path))\n \n for i, frame_path in enumerate(frame_files):\n image = cv2.imread(frame_path)\n image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n \n detection_results = face_detection.process(image_rgb)\n \n if detection_results.detections:\n for j, detection in enumerate(detection_results.detections):\n bbox = detection.location_data.relative_bounding_box\n results.append({\n \"frame_number\": i,\n \"face_id\": j,\n \"timestamp\": i * 0.5, # 2 FPS\n \"bbox\": {\n \"x\": bbox.xmin,\n \"y\": bbox.ymin,\n \"width\": bbox.width,\n \"height\": bbox.height\n },\n \"confidence\": detection.score[0],\n \"frame_path\": frame_path\n })\n\nwith open(f\"/tmp/faces_{workflow_id}.json\", \"w\") as f:\n json.dump(results, f)\n\nprint(json.dumps({\"faces_detected\": len(results), \"output_file\": f\"/tmp/faces_{workflow_id}.json\"}))\n`;\n\nreturn {\n json: {\n python_script: pythonScript,\n workflow_id: $json.workflow_id\n }\n};"
},
"id": "78c80c32-0826-4a48-988f-6b645ec9f08a",
"name": "Pr\u00e9parer D\u00e9tection Visages",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
-1100,
400
]
},
{
"parameters": {
"command": "python3 -c \"{{ $json.python_script }}\""
},
"id": "25c8e10a-e314-4ccd-94ab-d5eab43379be",
"name": "D\u00e9tecter Visages",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
-880,
400
]
},
{
"parameters": {
"functionCode": "// Lire et traiter les r\u00e9sultats de d\u00e9tection\nconst fs = require('fs');\nconst workflowId = $('Pr\u00e9parer D\u00e9tection Visages').first().json.workflow_id;\nconst facesFile = `/tmp/faces_${workflowId}.json`;\n\ntry {\n const facesData = JSON.parse(fs.readFileSync(facesFile, 'utf8'));\n \n // Grouper les visages par position pour identifier les diff\u00e9rentes personnes\n const speakers = [];\n const threshold = 0.3; // Seuil de similarit\u00e9 de position\n \n facesData.forEach(face => {\n let assigned = false;\n \n for (let speaker of speakers) {\n const avgX = speaker.faces.reduce((sum, f) => sum + f.bbox.x, 0) / speaker.faces.length;\n const avgY = speaker.faces.reduce((sum, f) => sum + f.bbox.y, 0) / speaker.faces.length;\n \n if (Math.abs(face.bbox.x - avgX) < threshold && Math.abs(face.bbox.y - avgY) < threshold) {\n speaker.faces.push(face);\n assigned = true;\n break;\n }\n }\n \n if (!assigned) {\n speakers.push({\n speaker_id: speakers.length,\n faces: [face],\n avg_position: { x: face.bbox.x, y: face.bbox.y }\n });\n }\n });\n \n return speakers.map(speaker => ({\n json: {\n speaker_id: speaker.speaker_id,\n face_count: speaker.faces.length,\n first_appearance: speaker.faces[0].timestamp,\n last_appearance: speaker.faces[speaker.faces.length - 1].timestamp,\n avg_position: speaker.avg_position,\n sample_frame: speaker.faces[0].frame_path,\n workflow_id: workflowId\n }\n }));\n \n} catch (error) {\n throw new Error(`Erreur lecture d\u00e9tection: ${error.message}`);\n}"
},
"id": "1baad5aa-f5b5-4dd2-a00e-5cc239437b99",
"name": "Traiter Visages D\u00e9tect\u00e9s",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
-660,
400
]
},
{
"parameters": {
"functionCode": "// Pr\u00e9parer la transformation en avatar enfant\nconst speakerData = $input.first().json;\n\n// Extraire le visage de l'image de r\u00e9f\u00e9rence\nconst extractCommand = `ffmpeg -i \"${speakerData.sample_frame}\" -vf \"crop=w*${speakerData.avg_position.x + 0.2}:h*${speakerData.avg_position.y + 0.2}:w*${speakerData.avg_position.x}:h*${speakerData.avg_position.y}\" /tmp/face_${speakerData.workflow_id}_${speakerData.speaker_id}.png`;\n\nreturn {\n json: {\n extract_command: extractCommand,\n face_file: `/tmp/face_${speakerData.workflow_id}_${speakerData.speaker_id}.png`,\n speaker_id: speakerData.speaker_id,\n workflow_id: speakerData.workflow_id\n }\n};"
},
"id": "20b3a7cf-b6a2-4728-ae77-89821a00ed7b",
"name": "Pr\u00e9parer Extraction Visage",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
-440,
400
]
},
{
"parameters": {
"command": "{{ $json.extract_command }}"
},
"id": "b19584ab-4c5d-4909-8794-a3d6fe56ddb0",
"name": "Extraire Visage",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
-220,
400
]
},
{
"parameters": {
"url": "https://api.replicate.com/v1/predictions",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Token YOUR_REPLICATE_TOKEN"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "version",
"value": "ac732df83cea7fff18b8472768c88ad041fa750ff7682a21affe81863cbe77e4"
},
{
"name": "input",
"value": "={\"image\": \"data:image/png;base64,{{ $binary.data.toString('base64') }}\", \"prompt\": \"cute child cartoon character, pixar disney style, bright colors, animated, friendly face\", \"num_outputs\": 1}"
}
]
},
"options": {}
},
"id": "73516958-94e2-4646-9a1e-654c399d4546",
"name": "Transformer en Enfant",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [
0,
400
]
},
{
"parameters": {
"functionCode": "// Pr\u00e9parer l'animation avec Wav2Lip\nconst transformResult = $input.first().json;\nconst speakerData = $('Pr\u00e9parer Extraction Visage').first().json;\n\n// Commande Wav2Lip pour animer le visage\nconst wav2lipCommand = `python wav2lip/inference.py --checkpoint_path checkpoints/wav2lip.pth --face \"${transformResult.output[0]}\" --audio \"/tmp/audio_${speakerData.workflow_id}.wav\" --outfile \"/tmp/animated_${speakerData.workflow_id}_${speakerData.speaker_id}.mp4\"`;\n\nreturn {\n json: {\n wav2lip_command: wav2lipCommand,\n animated_file: `/tmp/animated_${speakerData.workflow_id}_${speakerData.speaker_id}.mp4`,\n child_avatar: transformResult.output[0],\n speaker_id: speakerData.speaker_id,\n workflow_id: speakerData.workflow_id\n }\n};"
},
"id": "bee72df1-22bd-4ae1-8e80-1bf876a7e2e2",
"name": "Pr\u00e9parer Animation",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
-1100,
840
]
},
{
"parameters": {
"command": "{{ $json.wav2lip_command }}"
},
"id": "ae4cf970-cbf9-4ffd-8cb2-3c59e1b15928",
"name": "Animer Visage",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
-880,
840
]
},
{
"parameters": {
"functionCode": "// Assembler la vid\u00e9o finale\nconst animationData = $input.all();\nconst workflowId = animationData[0].json.workflow_id;\n\n// Cr\u00e9er la commande FFmpeg pour assembler toutes les animations\nlet overlayFilter = '[0:v]';\nlet inputs = `-i /tmp/video_${workflowId}.mp4`;\n\nanimationData.forEach((data, index) => {\n inputs += ` -i \"${data.json.animated_file}\"`;\n const nextIndex = index + 1;\n const outputLabel = index === animationData.length - 1 ? '[out]' : `[v${nextIndex}]`;\n overlayFilter += `overlay=0:0:enable='between(t,${data.json.start_time || 0},${data.json.end_time || 999})'${outputLabel}`;\n if (index < animationData.length - 1) {\n overlayFilter = `${outputLabel}`;\n }\n});\n\nconst finalCommand = `ffmpeg ${inputs} -filter_complex \"${overlayFilter}\" -map \"[out]\" -map 0:a -c:a copy /tmp/final_${workflowId}.mp4`;\n\nreturn {\n json: {\n final_command: finalCommand,\n final_video: `/tmp/final_${workflowId}.mp4`,\n workflow_id: workflowId,\n speakers_count: animationData.length\n }\n};"
},
"id": "60766743-d785-48f8-8a51-e3a3c00e82a3",
"name": "Pr\u00e9parer Assemblage Final",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
-660,
840
]
},
{
"parameters": {
"command": "{{ $json.final_command }}"
},
"id": "b0f33305-a1c6-42d0-abe9-91c0f6aa814f",
"name": "Assembler Vid\u00e9o Finale",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
-440,
840
]
},
{
"parameters": {
"functionCode": "// R\u00e9ponse finale avec les r\u00e9sultats\nconst finalData = $input.first().json;\n\nreturn {\n json: {\n success: true,\n message: \"Transformation termin\u00e9e avec succ\u00e8s\",\n final_video_path: finalData.final_video,\n workflow_id: finalData.workflow_id,\n speakers_processed: finalData.speakers_count,\n download_url: `http://your-server.com/download/${finalData.workflow_id}`,\n processing_time: new Date().toISOString()\n }\n};"
},
"id": "542a89e4-19ab-4b1b-8b9f-9922a056366a",
"name": "R\u00e9ponse Finale",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
-220,
840
]
},
{
"parameters": {
"content": "## Requirements:\n\n\nInstall dependencies\n\n```bash\nsudo apt update\nsudo apt install -y python3 python3-pip ffmpeg wget\n```\n\nInstall MediaPipe\n```bash\npip install mediapipe opencv-python\n```\n\nInstall Wav2Lip (optional - can be replaced by Replicate)\n```bash\ngit clone https://github.com/Rudrabha/Wav2Lip.git\ncd Wav2Lip\npip install -r requirements.txt\n```",
"height": 560,
"width": 520
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-2260,
120
],
"id": "3c06e575-1c85-4952-9084-c902934431f6",
"name": "Sticky Note"
},
{
"parameters": {
"options": {}
},
"type": "@n8n/n8n-nodes-langchain.chatTrigger",
"typeVersion": 1.1,
"position": [
-1160,
0
],
"id": "eb4efad9-4ad3-499a-9b5e-73a2d7d1173e",
"name": "When chat message received"
},
{
"parameters": {
"type": "SHA256",
"value": "={{ $json.data.id }}{{ $json.data.title }}",
"dataPropertyName": "hash"
},
"type": "n8n-nodes-base.crypto",
"typeVersion": 1,
"position": [
-440,
-300
],
"id": "47f9c2cc-1f78-42ff-bcd3-65b151e66d05",
"name": "Crypto"
},
{
"parameters": {
"operation": "append",
"documentId": {
"__rl": true,
"mode": "list",
"value": ""
},
"sheetName": {
"__rl": true,
"mode": "list",
"value": ""
}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.6,
"position": [
-220,
-380
],
"id": "aba50d9c-12fa-4bef-a914-961143144460",
"name": "Google Sheets",
"disabled": true
},
{
"parameters": {
"functionCode": "// Traitement de la r\u00e9ponse de t\u00e9l\u00e9chargement\nconst response = $input.first().json;\n\nif (response.code !== 0) {\n throw new Error('\u00c9chec du t\u00e9l\u00e9chargement TikTok');\n}\n\nconst data = response.data;\n \nreturn {\n json: {\n video_url: data.hdplay || data.play,\n video_title: data.title,\n duration: data.duration,\n author: data.author.nickname,\n workflow_id: $('Valider URL').first().json.workflow_id,\n download_success: true\n }\n};"
},
"id": "1938db66-6bb7-44d5-bf62-dc94e8e8ab22",
"name": "Edit Download",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
-440,
-100
]
},
{
"parameters": {
"height": 380,
"width": 1280
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-1120,
240
],
"id": "b25c02c7-5abd-4cbf-b555-c5501cc13454",
"name": "Sticky Note1"
},
{
"parameters": {
"height": 380,
"width": 1280
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-1120,
680
],
"id": "072c399c-5e8e-44fa-b9ae-b8c241038757",
"name": "Sticky Note2"
}
],
"connections": {
"Webhook TikTok": {
"main": [
[
{
"node": "Valider URL",
"type": "main",
"index": 0
}
]
]
},
"Valider URL": {
"main": [
[
{
"node": "T\u00e9l\u00e9charger TikTok",
"type": "main",
"index": 0
}
]
]
},
"T\u00e9l\u00e9charger TikTok": {
"main": [
[
{
"node": "Edit Download",
"type": "main",
"index": 0
},
{
"node": "Crypto",
"type": "main",
"index": 0
}
]
]
},
"Sauvegarder Vid\u00e9o": {
"main": [
[
{
"node": "Extraire Audio",
"type": "main",
"index": 0
},
{
"node": "Extraire Frames",
"type": "main",
"index": 0
}
]
]
},
"Extraire Frames": {
"main": [
[
{
"node": "Pr\u00e9parer D\u00e9tection Visages",
"type": "main",
"index": 0
}
]
]
},
"Pr\u00e9parer D\u00e9tection Visages": {
"main": [
[
{
"node": "D\u00e9tecter Visages",
"type": "main",
"index": 0
}
]
]
},
"D\u00e9tecter Visages": {
"main": [
[
{
"node": "Traiter Visages D\u00e9tect\u00e9s",
"type": "main",
"index": 0
}
]
]
},
"Traiter Visages D\u00e9tect\u00e9s": {
"main": [
[
{
"node": "Pr\u00e9parer Extraction Visage",
"type": "main",
"index": 0
}
]
]
},
"Pr\u00e9parer Extraction Visage": {
"main": [
[
{
"node": "Extraire Visage",
"type": "main",
"index": 0
}
]
]
},
"Extraire Visage": {
"main": [
[
{
"node": "Transformer en Enfant",
"type": "main",
"index": 0
}
]
]
},
"Transformer en Enfant": {
"main": [
[
{
"node": "Pr\u00e9parer Animation",
"type": "main",
"index": 0
}
]
]
},
"Pr\u00e9parer Animation": {
"main": [
[
{
"node": "Animer Visage",
"type": "main",
"index": 0
}
]
]
},
"Animer Visage": {
"main": [
[
{
"node": "Pr\u00e9parer Assemblage Final",
"type": "main",
"index": 0
}
]
]
},
"Pr\u00e9parer Assemblage Final": {
"main": [
[
{
"node": "Assembler Vid\u00e9o Finale",
"type": "main",
"index": 0
}
]
]
},
"Assembler Vid\u00e9o Finale": {
"main": [
[
{
"node": "R\u00e9ponse Finale",
"type": "main",
"index": 0
}
]
]
},
"When chat message received": {
"main": [
[
{
"node": "Valider URL",
"type": "main",
"index": 0
}
]
]
},
"Crypto": {
"main": [
[
{
"node": "Google Sheets",
"type": "main",
"index": 0
}
]
]
},
"Edit Download": {
"main": [
[
{
"node": "Sauvegarder Vid\u00e9o",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
},
"staticData": null,
"meta": null,
"versionId": "d847dfbd-6946-44e6-8f93-67ec968b5b24",
"triggerCount": 0,
"tags": []
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Tiktok. Uses httpRequest, executeCommand, chatTrigger, crypto. Webhook trigger; 24 nodes.
Source: https://github.com/tsnaketech/n8n-backups/blob/031172207e7f8bb6867cae683234c82545346626/workflows/Tiktok.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Automated Tiktok Videos. Uses executeCommand, httpRequest, lmChatGroq, chainLlm. Webhook trigger; 45 nodes.
Automate Facebook Messenger orders to Google Sheets and Google Calendar
📄 Documentation: Notion Guide
This n8n workflow template automates the entire process of publishing Instagram Reels from content stored in Google Sheets and Google Drive. It's designed for content creators, social media managers,
Instagram - Fluxo de mensagens. Uses rabbitmq, rabbitmqTrigger, googleSheets, httpRequest. Webhook trigger; 74 nodes.