{"record":{"id":"bdabf6752e477c8b","repo":"jackwener/OpenCLI","slug":"video-jobs-support-video-raw-video-social-or-gif","errorCode":null,"errorMessage":"Video jobs support video-raw, video-social, or gif downloads","messagePattern":"Video jobs support video-raw, video-social, or gif downloads","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/midjourney/download.js","lineNumber":49,"sourceCode":"    { name: 'job', positional: true, required: true, help: 'Job UUID or Midjourney /jobs/<uuid> URL' },\n    { name: 'index', default: 'all', help: 'Candidate 1..4 or all' },\n    { name: 'kind', default: 'auto', help: 'auto, image, video-raw, video-social, or gif' },\n    { name: 'output', default: '~/Pictures/Midjourney', help: 'Output directory' },\n    { name: 'force', type: 'boolean', default: false, help: 'Overwrite existing non-empty files' },\n  ],\n  columns: ['job_id', 'status', 'kind', 'index', 'file', 'bytes', 'mime', 'url'],\n  func: async (page, kwargs) => {\n    const jobId = parseJobId(kwargs.job);\n    const job = await fetchJobStatus(page, jobId);\n    const status = String(job.current_status || job.status || '').toLowerCase();\n    if (status !== 'completed') {\n      throw new CommandExecutionError(`Midjourney job ${jobId} is \"${status || 'missing'}\"; media is available after completion.`);\n    }\n    const video = isVideoJob(job);\n    let kind = String(kwargs.kind || 'auto').trim().toLowerCase();\n    if (!KINDS.includes(kind)) throw new ArgumentError(`--kind must be one of: ${KINDS.join(', ')}`);\n    if (kind === 'auto') kind = video ? 'video-raw' : 'image';\n    if (video && kind === 'image') throw new ArgumentError('Video jobs support video-raw, video-social, or gif downloads');\n    if (!video && kind !== 'image') throw new ArgumentError('Image jobs only support --kind image');\n\n    const indices = parseImageIndices(kwargs.index, Number(job.batch_size || (video ? 1 : 4)));\n    const outputDir = resolveOutputDir(kwargs.output);\n    const force = normalizeBoolean(kwargs.force);\n    let files;\n    if (kind === 'image') {\n      files = (await downloadOriginals(page, jobId, indices, outputDir, force)).map((item) => ({\n        ...item,\n        kind: 'image',\n      }));\n    } else if (kind === 'video-raw') {\n      files = [];\n      for (const index of indices) files.push(await downloadRawVideo(page, jobId, index, outputDir, force));\n    } else {\n      files = [];\n      for (const index of indices) files.push(await downloadRenderedVideo(page, jobId, index, kind, outputDir, force));\n    }","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/midjourney/download.js#L31-L67","documentation":"This ArgumentError is thrown when --kind image is requested for a job that isVideoJob(job) identifies as a video job. Video jobs only expose video-raw, video-social, or gif downloads (auto also resolves to video-raw), so requesting a still image for a video job is rejected.","triggerScenarios":"Calling `opencli midjourney download <job> --kind image` where the fetched job matches isVideoJob — i.e. the job was created by a video generation (e.g. an animated/zoom job), not a 4-image grid.","commonSituations":"Hardcoding --kind image in a script that also processes video jobs; assuming every Midjourney job produces stills; reusing an image-download command against a video job ID; --kind auto resolving unexpectedly and then being manually overridden to image.","solutions":["Drop --kind (or use --kind auto) so video jobs automatically download as video-raw","Use --kind video-raw for the original video, --kind video-social for the social MP4, or --kind gif for an animated GIF","Check the job type first (isVideoJob / job details) before choosing --kind","Separate image and video job IDs into different download batches in scripts"],"exampleFix":"// before\nopencli midjourney download abc123 --kind image\n// after\nopencli midjourney download abc123 --kind video-raw","handlingStrategy":"type-guard","validationCode":"// Resolve job type before choosing --kind\nconst job = await fetchJobStatus(page, parseJobId(jobRef));\nconst video = isVideoJob(job);\nconst requested = String(kind || 'auto').trim().toLowerCase();\nif (video && (requested === 'image')) {\n  throw new Error('Job is a video; use video-raw, video-social, or gif.');\n}\nif (!video && requested !== 'auto' && requested !== 'image') {\n  throw new Error('Job is an image; only --kind image is supported.');\n}","typeGuard":"function isVideoJobType(job) {\n  return isVideoJob(job);\n}\n// usage: const kind = isVideoJobType(job) ? 'video-raw' : 'image';","tryCatchPattern":"try {\n  await run('midjourney', 'download', jobId, ['--kind', kind]);\n} catch (err) {\n  if (err.message.includes('Video jobs support')) {\n    // retry with --kind auto or video-raw\n  } else throw err;\n}","preventionTips":["Use --kind auto to let the CLI match the job type automatically","Check whether the job is a video job before forcing --kind image","Keep image and video job IDs in separate download batches in scripts","Remember: video jobs accept video-raw/video-social/gif; image jobs accept only image"],"tags":["argument-validation","kind-mismatch","video","midjourney"],"backgroundTag":"invalid-kind-for-job-type","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}