ChartQuery

Extraer audio de un video

Descarga solo la pista de audio de cualquier URL de video como archivo MP3 usando HuntAPI — ideal para archivado de podcasts, pipelines de transcripción o reutilización de contenido.

Descripción general

Al pasar quality: "audio" al descargador HuntAPI, obtienes un extracto MP3 en lugar del video completo. Este tutorial construye una pipeline completa de extracción de audio incluyendo envío del trabajo, consulta y guardado.

Prerrequisitos

  • Una clave de API de HuntAPI — consigue una en app.huntapi.com
  • Instala las dependencias para tu lenguaje:
pip install requests

Sin dependencias adicionales — usa la API nativa fetch (Node 18+).

Extensión curl habilitada (activa por defecto).

Sin dependencias adicionales — usa net/http (Go 1.18+).

Sin dependencias adicionales — usa java.net.http (Java 11+).

Sin dependencias adicionales — usa System.Net.Http (.NET 6+).

# Cargo.toml
[dependencies]
reqwest = { version = "0.12", features = ["json"] }
tokio = { version = "1", features = ["full"] }
serde_json = "1"

Pasos

Envía un trabajo de extracción de audio

Usa el mismo endpoint /v1/video/download con quality=audio.

import requests, time

API_KEY   = "YOUR_API_KEY"
VIDEO_URL = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"

response = requests.get(
    "https://api.huntapi.com/v1/video/download",
    headers={"x-api-key": API_KEY},
    params={"url": VIDEO_URL, "quality": "audio"},
)
job_id = response.json()["job_id"]
print(f"Trabajo enviado: {job_id}")
const API_KEY   = "YOUR_API_KEY";
const VIDEO_URL = "https://www.youtube.com/watch?v=dQw4w9WgXcQ";

const params   = new URLSearchParams({ url: VIDEO_URL, quality: "audio" });
const response = await fetch(`https://api.huntapi.com/v1/video/download?${params}`, {
  headers: { "x-api-key": API_KEY },
});
const jobId = (await response.json()).job_id;
console.log(`Trabajo enviado: ${jobId}`);
<?php
$apiKey   = "YOUR_API_KEY";
$videoUrl = "https://www.youtube.com/watch?v=dQw4w9WgXcQ";

$params = http_build_query(["url" => $videoUrl, "quality" => "audio"]);
$ch     = curl_init("https://api.huntapi.com/v1/video/download?{$params}");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["x-api-key: {$apiKey}"]);
$jobId = json_decode(curl_exec($ch), true)["job_id"];
curl_close($ch);
echo "Trabajo enviado: {$jobId}\n";
package main

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "net/url"
    "os"
    "time"
)

const APIKey   = "YOUR_API_KEY"
const VideoURL = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"

func submitJob(apiKey, videoURL, quality string) string {
    params := url.Values{"url": {videoURL}, "quality": {quality}}
    req, _ := http.NewRequest("GET", "https://api.huntapi.com/v1/video/download?"+params.Encode(), nil)
    req.Header.Set("x-api-key", apiKey)
    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()
    body, _ := io.ReadAll(resp.Body)
    var data map[string]any
    json.Unmarshal(body, &data)
    return data["job_id"].(string)
}

func main() {
    jobID := submitJob(APIKey, VideoURL, "audio")
    fmt.Println("Trabajo enviado:", jobID)
    // continúa en el siguiente paso...
    _ = jobID
    _ = os.Args
    _ = time.Second
}
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.*;
import java.nio.charset.StandardCharsets;
import org.json.*;

var apiKey   = "YOUR_API_KEY";
var videoUrl = URLEncoder.encode("https://www.youtube.com/watch?v=dQw4w9WgXcQ", StandardCharsets.UTF_8);
var url      = "https://api.huntapi.com/v1/video/download?url=" + videoUrl + "&quality=audio";

var client  = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder().uri(URI.create(url))
    .header("x-api-key", apiKey).GET().build();

var response = client.send(request, HttpResponse.BodyHandlers.ofString());
var jobId    = new JSONObject(response.body()).getString("job_id");
System.out.println("Trabajo enviado: " + jobId);
using System.Net.Http;
using System.Text.Json;

var apiKey   = "YOUR_API_KEY";
var videoUrl = Uri.EscapeDataString("https://www.youtube.com/watch?v=dQw4w9WgXcQ");

using var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key", apiKey);

var body  = await client.GetStringAsync($"https://api.huntapi.com/v1/video/download?url={videoUrl}&quality=audio");
var jobId = JsonDocument.Parse(body).RootElement.GetProperty("job_id").GetString()!;
Console.WriteLine($"Trabajo enviado: {jobId}");
use reqwest::Client;
use serde_json::Value;

#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
    let client    = Client::new();
    let api_key   = "YOUR_API_KEY";
    let video_url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ";

    let data = client.get("https://api.huntapi.com/v1/video/download")
        .header("x-api-key", api_key)
        .query(&[("url", video_url), ("quality", "audio")])
        .send().await?.json::<Value>().await?;

    let job_id = data["job_id"].as_str().unwrap();
    println!("Trabajo enviado: {}", job_id);
    Ok(())
}

Consulta el estado y descarga el MP3

Consulta el estado del trabajo, espera a que sea "done", luego descarga el archivo MP3.

def wait_and_download(job_id: str, output_file: str = "audio.mp3") -> None:
    start = time.time()
    while time.time() - start < 300:
        r      = requests.get(f"https://api.huntapi.com/v1/job/{job_id}",
            headers={"x-api-key": API_KEY})
        result = r.json()
        status = result.get("status")
        print(f"  Estado: {status}")

        if status == "done":
            download_url = result["download_url"]
            with requests.get(download_url, stream=True) as dl:
                dl.raise_for_status()
                with open(output_file, "wb") as f:
                    for chunk in dl.iter_content(8192):
                        f.write(chunk)
            print(f"✓ Audio guardado en {output_file}")
            return

        if status == "error":
            raise RuntimeError(f"Trabajo fallido: {result.get('error')}")
        time.sleep(5)
    raise TimeoutError("Tiempo límite alcanzado")

wait_and_download(job_id)
import { createWriteStream } from "fs";
import { Readable } from "stream";

async function waitAndDownload(jobId: string, outputFile = "audio.mp3") {
  const deadline = Date.now() + 300_000;
  while (Date.now() < deadline) {
    const res    = await fetch(`https://api.huntapi.com/v1/job/${jobId}`, {
      headers: { "x-api-key": API_KEY },
    });
    const result = await res.json();
    console.log(`  Estado: ${result.status}`);

    if (result.status === "done") {
      const dl     = await fetch(result.download_url);
      const writer = createWriteStream(outputFile);
      Readable.fromWeb(dl.body as any).pipe(writer);
      await new Promise((r, e) => { writer.on("finish", r); writer.on("error", e); });
      console.log(`✓ Audio guardado en ${outputFile}`);
      return;
    }
    if (result.status === "error") throw new Error(`Trabajo fallido: ${result.error}`);
    await new Promise(r => setTimeout(r, 5000));
  }
  throw new Error("Tiempo límite alcanzado");
}

await waitAndDownload(jobId);
function waitAndDownload(string $apiKey, string $jobId, string $outputFile = "audio.mp3"): void {
    $start = time();
    while (time() - $start < 300) {
        $ch = curl_init("https://api.huntapi.com/v1/job/{$jobId}");
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, ["x-api-key: {$apiKey}"]);
        $result = json_decode(curl_exec($ch), true);
        curl_close($ch);

        $status = $result["status"] ?? "";
        echo "  Estado: {$status}\n";

        if ($status === "done") {
            $fp = fopen($outputFile, "wb");
            $ch = curl_init($result["download_url"]);
            curl_setopt($ch, CURLOPT_FILE, $fp);
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
            curl_exec($ch);
            curl_close($ch);
            fclose($fp);
            echo "✓ Audio guardado en {$outputFile}\n";
            return;
        }
        if ($status === "error") throw new RuntimeException("Trabajo fallido: " . ($result["error"] ?? ""));
        sleep(5);
    }
    throw new RuntimeException("Tiempo límite alcanzado");
}

waitAndDownload($apiKey, $jobId);
func waitAndDownload(apiKey, jobID, outputFile string) error {
    deadline := time.Now().Add(5 * time.Minute)
    for time.Now().Before(deadline) {
        req, _ := http.NewRequest("GET", "https://api.huntapi.com/v1/job/"+jobID, nil)
        req.Header.Set("x-api-key", apiKey)
        resp, _ := http.DefaultClient.Do(req)
        body, _ := io.ReadAll(resp.Body)
        resp.Body.Close()

        var result map[string]any
        json.Unmarshal(body, &result)
        status := result["status"].(string)
        fmt.Println("  Estado:", status)

        if status == "done" {
            dlResp, _ := http.Get(result["download_url"].(string))
            defer dlResp.Body.Close()
            f, _ := os.Create(outputFile)
            io.Copy(f, dlResp.Body)
            f.Close()
            fmt.Println("✓ Audio guardado en", outputFile)
            return nil
        }
        if status == "error" { return fmt.Errorf("trabajo fallido: %v", result["error"]) }
        time.Sleep(5 * time.Second)
    }
    return fmt.Errorf("tiempo límite alcanzado")
}

if err := waitAndDownload(APIKey, jobID, "audio.mp3"); err != nil { panic(err) }
import java.io.*;
import java.net.*;
import java.nio.file.*;
import java.time.*;

static void waitAndDownload(HttpClient client, String apiKey, String jobId, String outputFile) throws Exception {
    var deadline = Instant.now().plusSeconds(300);
    while (Instant.now().isBefore(deadline)) {
        var req    = HttpRequest.newBuilder()
            .uri(URI.create("https://api.huntapi.com/v1/job/" + jobId))
            .header("x-api-key", apiKey).GET().build();
        var result = new JSONObject(client.send(req, HttpResponse.BodyHandlers.ofString()).body());
        var status = result.getString("status");
        System.out.println("  Estado: " + status);

        if ("done".equals(status)) {
            Files.copy(new URL(result.getString("download_url")).openStream(),
                Path.of(outputFile), StandardCopyOption.REPLACE_EXISTING);
            System.out.println("✓ Audio guardado en " + outputFile);
            return;
        }
        if ("error".equals(status)) throw new RuntimeException("Trabajo fallido: " + result.optString("error"));
        Thread.sleep(5000);
    }
    throw new RuntimeException("Tiempo límite alcanzado");
}

waitAndDownload(client, apiKey, jobId, "audio.mp3");
async Task WaitAndDownload(HttpClient client, string jobId, string outputFile = "audio.mp3")
{
    var deadline = DateTime.UtcNow.AddMinutes(5);
    while (DateTime.UtcNow < deadline)
    {
        var body   = await client.GetStringAsync($"https://api.huntapi.com/v1/job/{jobId}");
        var result = JsonDocument.Parse(body).RootElement;
        var status = result.GetProperty("status").GetString();
        Console.WriteLine($"  Estado: {status}");

        if (status == "done") {
            var dlUrl  = result.GetProperty("download_url").GetString()!;
            var stream = await new HttpClient().GetStreamAsync(dlUrl);
            using var file = File.Create(outputFile);
            await stream.CopyToAsync(file);
            Console.WriteLine($"✓ Audio guardado en {outputFile}");
            return;
        }
        if (status == "error") throw new Exception($"Trabajo fallido: {result.GetProperty("error")}");
        await Task.Delay(5000);
    }
    throw new TimeoutException("Tiempo límite alcanzado.");
}

await WaitAndDownload(client, jobId);
use reqwest::Client;
use serde_json::Value;
use std::{fs::File, io::Write, time::{Duration, Instant}};
use tokio::time::sleep;

async fn wait_and_download(client: &Client, api_key: &str, job_id: &str, output: &str) {
    let deadline = Instant::now() + Duration::from_secs(300);
    loop {
        assert!(Instant::now() < deadline, "Tiempo límite alcanzado");
        let result = client.get(format!("https://api.huntapi.com/v1/job/{}", job_id))
            .header("x-api-key", api_key)
            .send().await.unwrap().json::<Value>().await.unwrap();
        let status = result["status"].as_str().unwrap_or("");
        println!("  Estado: {}", status);

        if status == "done" {
            let bytes = client.get(result["download_url"].as_str().unwrap())
                .send().await.unwrap().bytes().await.unwrap();
            let mut f = File::create(output).unwrap();
            f.write_all(&bytes).unwrap();
            println!("✓ Audio guardado en {}", output);
            return;
        }
        if status == "error" { panic!("Trabajo fallido: {}", result["error"]); }
        sleep(Duration::from_secs(5)).await;
    }
}

#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
    let client  = Client::new();
    let api_key = "YOUR_API_KEY";
    let data    = client.get("https://api.huntapi.com/v1/video/download")
        .header("x-api-key", api_key)
        .query(&[("url", "https://www.youtube.com/watch?v=dQw4w9WgXcQ"), ("quality", "audio")])
        .send().await?.json::<Value>().await?;
    let job_id = data["job_id"].as_str().unwrap();
    wait_and_download(&client, api_key, job_id, "audio.mp3").await;
    Ok(())
}

El MP3 resultante contiene los metadatos del audio original (título, artista, portada del álbum) cuando están disponibles. Es ideal para pipelines de transcripción — pasa directamente el archivo a Whisper u otro servicio de STT.

En esta página