﻿---
title: "Новые лимиты Suno на скачивание треков и как забрать свои песни"
description: "Разбираем жесткую систему квот на экспорт из Suno и делимся удобным инструментом для скачивания любой композиции в MP3 или M4A в один клик."
date: 2026-10-07
tags: ["Веб-разработка и софт", "Suno", "Скрипты", "Web Crypto"]
author: "Иван Чистин"
---

<BackgroundGlow color="#F2BEFC" position="top-right" size="lg" />

С **3 сентября 2026 года** сервис Suno кардинально изменил правила игры и ввёл жесткую систему квот на экспорт готовых аудиофайлов.

Если раньше генерация треков расходовала привычные кредиты, а скачивать получившиеся композиции можно было без особых препятствий, то теперь возможность сохранить аудио на своё устройство строго привязана к тарифному плану:

| Тариф | Лимит на скачивание | Коммерческие права |
| :--- | :--- | :--- |
| **Free (Бесплатный)** | **7 скачиваний всего** (trial / lifetime на весь срок жизни аккаунта) | Нет (только личное некоммерческое использование) |
| **Pro (~$10 / мес)** | **20 скачиваний** в месяц | Да (включая стриминги и коммерческое использование) |
| **Premier (~$30 / мес)** | **60 скачиваний** в месяц | Да |
| **Premier + Suno Studio** | **Безлимитно** | Да |

### Ключевые нюансы работы новых лимитов:

1. **Квоты действуют ретроактивно:** ограничения распространяются на всю вашу медиатеку, включая старые песни, сгенерированные задолго до нововведения.
2. **Повторные загрузки не сжигают счетчик:** повторное скачивание одного и того же трека (или скачивание отдельных стемов/дорожек одной композиции) учитывается как **1** скачивание.
3. **Лимиты не переносятся:** неиспользованные за месяц квоты на тарифах Pro и Premier сгорают и не суммируются со следующим периодом.
4. **Прослушивание и шеринг остаются полностью бесплатными:** лимиты касаются исключительно экспорта файлов (MP3 / WAV / M4A / видео). Внутри веб-плеера и мобильного приложения Suno композиции по-прежнему играют без каких-либо ограничений.

---

## Скачать трек прямо сейчас

Поскольку внутри браузера воспроизведение треков остаётся бесплатным и безлимитным, всю музыку можно свободно сохранить на компьютер или телефон. 

Вам не нужно ничего настраивать, устанавливать программы или разбираться в коде — просто вставьте ссылку на песню:

<div class="my-8 rounded-3xl border border-[#EAD5E6] bg-gradient-to-br from-white via-[#FAF5F9] to-[#F3E0EC]/60 p-6 sm:p-8 shadow-sm">
  <div class="flex items-center gap-3 mb-4">
    <div class="w-3.5 h-3.5 rounded-full bg-[#CA9CE1] animate-pulse"></div>
    <h3 class="text-lg sm:text-xl font-black text-[#18141F] m-0">
      Загрузчик песен из Suno
    </h3>
  </div>

  <p class="text-xs sm:text-sm text-[#685F74] mb-4">
    Вставьте обычную ссылку (например, <code class="bg-white px-2 py-0.5 rounded border border-[#EAD5E6]">https://suno.com/song/...</code>) или короткую ссылку вида <code class="bg-white px-2 py-0.5 rounded border border-[#EAD5E6]">https://suno.com/s/...</code>:
  </p>

  <div class="flex flex-col sm:flex-row gap-3 mb-3">
    <input
      type="text"
      id="suno-client-input"
      placeholder="https://suno.com/s/Lwtx3B8DASN9yX6i"
      class="flex-1 bg-white border border-[#EAD5E6] rounded-2xl px-4 py-3 text-sm text-[#18141F] focus:outline-none focus:border-[#CA9CE1] shadow-inner font-mono"
    />
    <button
      id="suno-client-btn"
      class="bg-[#18141F] hover:bg-[#241E2C] text-white font-bold text-sm px-6 py-3 rounded-2xl transition-all shadow-sm active:scale-95 flex items-center justify-center gap-2 cursor-pointer whitespace-nowrap"
    >
      <span>Скачать песню</span>
      <span id="suno-btn-spinner" class="hidden">⏳</span>
    </button>
  </div>

  {/* Переключатель формата */}
  <div class="flex items-center gap-5 text-xs text-[#685F74] mb-4 px-1">
    <span class="font-bold text-[#18141F]">Формат аудио:</span>
    <label class="flex items-center gap-1.5 cursor-pointer">
      <input type="radio" name="suno-format" value="mp3" checked class="accent-[#CA9CE1]" />
      <span><strong>MP3</strong> (универсальный)</span>
    </label>
    <label class="flex items-center gap-1.5 cursor-pointer">
      <input type="radio" name="suno-format" value="m4a" class="accent-[#CA9CE1]" />
      <span><strong>M4A</strong> (оригинал Opus, мгновенно)</span>
    </label>
  </div>

  {/* Статус бар */}
  <div id="suno-status-box" class="hidden p-4 rounded-2xl border border-[#EAD5E6] bg-white text-xs space-y-2">
    <div class="flex justify-between items-center text-[#685F74]">
      <span id="suno-status-text">Подготовка...</span>
      <span id="suno-status-pct" class="font-bold text-[#18141F]">0%</span>
    </div>
    <div class="w-full bg-[#FAF5F9] rounded-full h-2 overflow-hidden border border-[#EAD5E6]">
      <div id="suno-progress-bar" class="bg-gradient-to-r from-[#CA9CE1] to-[#F2BEFC] h-2 transition-all duration-200" style="width: 0%"></div>
    </div>
  </div>
</div>

{/* Подключение легковесного MP3-энкодера */}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lamejs/1.2.1/lame.all.js" is:inline></script>

<script is:inline>
{`
(function() {
  const WORKER_BASE = 'https://polished-water-1251.zxcvbnmqwe245.workers.dev';
  const btn = document.getElementById('suno-client-btn');
  const input = document.getElementById('suno-client-input');
  const statusBox = document.getElementById('suno-status-box');
  const statusText = document.getElementById('suno-status-text');
  const statusPct = document.getElementById('suno-status-pct');
  const progressBar = document.getElementById('suno-progress-bar');
  const spinner = document.getElementById('suno-btn-spinner');

  function updateStatus(text, pct) {
    statusBox.classList.remove('hidden');
    statusText.textContent = text;
    statusPct.textContent = pct + '%';
    progressBar.style.width = pct + '%';
  }

  function b64ToUint8(str) {
    const binary = atob(str);
    const bytes = new Uint8Array(binary.length);
    for (let i = 0; i < binary.length; i++) {
      bytes[i] = binary.charCodeAt(i);
    }
    return bytes;
  }

  async function convertToMp3(m4aBuffer, onProgress) {
    onProgress('Декодирование аудио...', 88);
    const AudioCtx = window.AudioContext || window.webkitAudioContext;
    const audioCtx = new AudioCtx();
    const audioBuffer = await audioCtx.decodeAudioData(m4aBuffer.slice(0));

    onProgress('Конвертация в MP3...', 92);
    const numChannels = audioBuffer.numberOfChannels;
    const sampleRate = audioBuffer.sampleRate;
    const mp3encoder = new lamejs.Mp3Encoder(numChannels, sampleRate, 192);

    const left = audioBuffer.getChannelData(0);
    const right = numChannels > 1 ? audioBuffer.getChannelData(1) : left;
    const len = left.length;

    const left16 = new Int16Array(len);
    const right16 = new Int16Array(len);

    for (let i = 0; i < len; i++) {
      left16[i] = Math.max(-32768, Math.min(32767, left[i] * 32767));
      right16[i] = Math.max(-32768, Math.min(32767, right[i] * 32767));
    }

    const mp3Data = [];
    const chunkSize = 1152 * 4;

    for (let i = 0; i < len; i += chunkSize) {
      const lChunk = left16.subarray(i, i + chunkSize);
      const rChunk = right16.subarray(i, i + chunkSize);
      let buf;
      if (numChannels === 1) {
        buf = mp3encoder.encodeBuffer(lChunk);
      } else {
        buf = mp3encoder.encodeBuffer(lChunk, rChunk);
      }
      if (buf.length > 0) mp3Data.push(buf);
    }

    const flushBuf = mp3encoder.flush();
    if (flushBuf.length > 0) mp3Data.push(flushBuf);

    return new Blob(mp3Data, { type: 'audio/mp3' });
  }

  if (btn) {
    btn.addEventListener('click', async () => {
      const val = (input.value || '').trim();
      if (!val) {
        alert('Пожалуйста, вставьте ссылку на композицию Suno.');
        return;
      }

      btn.disabled = true;
      spinner.classList.remove('hidden');

      try {
        let clipId = null;

        const directMatch = val.match(/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/);
        if (directMatch) {
          clipId = directMatch[0];
        } else if (val.includes('/s/')) {
          updateStatus('Определение трека по короткой ссылке...', 10);
          const resolveRes = await fetch(WORKER_BASE + '/resolve?url=' + encodeURIComponent(val));
          const resolveData = await resolveRes.json();
          const resolvedMatch = (resolveData.finalUrl || '').match(/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/);
          if (resolvedMatch) {
            clipId = resolvedMatch[0];
          }
        }

        if (!clipId) {
          throw new Error('Не удалось распознать ID песни из этой ссылки.');
        }

        updateStatus('Получение названия композиции...', 25);
        let title = 'Suno Track';
        let artist = '';
        try {
          const metaRes = await fetch(WORKER_BASE + '/api/clip/' + clipId);
          if (metaRes.ok) {
            const meta = await metaRes.json();
            title = meta.title || title;
            artist = meta.display_name ? (meta.display_name + ' - ') : '';
          }
        } catch (_) {}

        updateStatus('Получение ключей расшифровки...', 40);
        const mangoReq = await fetch(WORKER_BASE + '/api/mango/rights', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ content_params: { content_id: clipId, content_type: 'clip' } })
        });

        if (!mangoReq.ok) {
          throw new Error('Не удалось получить ключ расшифровки (код ' + mangoReq.status + ').');
        }

        const mData = await mangoReq.json();
        const glt = mData.glt;
        const rawKey = mData.key;
        const rawIv = mData.iv;

        updateStatus('Снятие криптографической защиты...', 55);
        const userKeyHash = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(glt));
        const userKey = await crypto.subtle.importKey('raw', userKeyHash, { name: 'AES-GCM' }, false, ['decrypt']);

        const wKey = b64ToUint8(rawKey);
        const wIv = b64ToUint8(rawIv);
        const aad = new TextEncoder().encode(clipId);

        const contentKey = await crypto.subtle.decrypt(
          { name: 'AES-GCM', iv: wKey.slice(0, 12), additionalData: aad },
          userKey,
          wKey.slice(12)
        );
        const contentIv = await crypto.subtle.decrypt(
          { name: 'AES-GCM', iv: wIv.slice(0, 12), additionalData: aad },
          userKey,
          wIv.slice(12)
        );

        updateStatus('Скачивание аудиодорожки...', 70);
        const streamUrl = 'https://d2lwuy8qc234o3.cloudfront.net/1/clip/' + clipId + '.m4a';
        const audioResp = await fetch(streamUrl);
        if (!audioResp.ok) {
          throw new Error('Ошибка загрузки аудиопотока (код ' + audioResp.status + ')');
        }

        const contentLength = Number(audioResp.headers.get('content-length')) || 0;
        let encryptedBuf;

        if (audioResp.body && contentLength > 0) {
          const reader = audioResp.body.getReader();
          const chunks = [];
          let received = 0;
          while (true) {
            const { done, value } = await reader.read();
            if (done) break;
            chunks.push(value);
            received += value.length;
            const pct = Math.min(85, Math.floor(70 + (received / contentLength) * 15));
            updateStatus('Скачивание аудио (' + Math.round(received / 1024) + ' KB)...', pct);
          }
          const allChunks = new Uint8Array(received);
          let pos = 0;
          for (const c of chunks) {
            allChunks.set(c, pos);
            pos += c.length;
          }
          encryptedBuf = allChunks.buffer;
        } else {
          encryptedBuf = await audioResp.arrayBuffer();
        }

        updateStatus('Расшифровка трека...', 86);
        const aesCtrKey = await crypto.subtle.importKey('raw', contentKey, { name: 'AES-CTR' }, false, ['decrypt']);
        const decryptedAudio = await crypto.subtle.decrypt(
          { name: 'AES-CTR', counter: new Uint8Array(contentIv), length: 128 },
          aesCtrKey,
          encryptedBuf
        );

        const formatRadio = document.querySelector('input[name="suno-format"]:checked');
        const selectedFormat = formatRadio ? formatRadio.value : 'mp3';

        let outBlob;
        let ext = 'm4a';

        if (selectedFormat === 'mp3' && window.lamejs) {
          try {
            outBlob = await convertToMp3(decryptedAudio, updateStatus);
            ext = 'mp3';
          } catch (e) {
            console.warn('Ошибка конвертации MP3, сохраняем оригинальный M4A:', e);
            outBlob = new Blob([decryptedAudio], { type: 'audio/mp4' });
            ext = 'm4a';
          }
        } else {
          outBlob = new Blob([decryptedAudio], { type: 'audio/mp4' });
          ext = 'm4a';
        }

        updateStatus('Готово! Файл сохраняется...', 100);
        const a = document.createElement('a');
        a.href = URL.createObjectURL(outBlob);
        const cleanName = (artist + title).replace(/[\\\\/*?:\"<>|]/g, '_');
        a.download = cleanName + '.' + ext;
        a.click();
      } catch (err) {
        alert(err.message || 'Произошла непредвиденная ошибка при загрузке.');
        updateStatus('Ошибка: ' + err.message, 0);
      } finally {
        btn.disabled = false;
        spinner.classList.add('hidden');
      }
    });
  }
})();
`}
</script>
