Tag: video datasets

  • Download YouTube Video Datasets: Panda-70M, HD-VILA, InternVid

    Panda-70M, HD-VILA-100M, InternVid and most other large video-language datasets don't ship any video. You get YouTube IDs, clip timestamps and captions, and you have to collect the videos yourself. This guide covers what each dataset actually releases (checked against the paper and the official repo), which tooling works for it, and how to plan storage, failures and scaling before you start a multi-terabyte download.

    TL;DR

    • Panda-70M, HD-VILA-100M, InternVid, HowTo100M, AudioSet and VoxCeleb2 all release metadata only (IDs, timestamps, text or labels). Kinetics-700-2020 is the exception: CVDF hosts the video files.
    • For Panda-70M, use the video2dataset fork inside the Panda-70M repo. The authors say upstream video2dataset can't read their CSV format.
    • Many clips per source video: download the video once and cut it locally with ffmpeg. One short segment per video: use yt-dlp --download-sections.
    • Expect missing videos. Kinetics-700-2020's own download log shows about 2% of videos already gone a few months after the 2020 release.
    • Storage ≈ hours × 3600 × bitrate ÷ 8. Panda-70M's own figure (~36 TB for 167k hours at 360p) works out to roughly 0.48 Mbps.
    • Annotation licenses don't give you any rights to the underlying videos.

    Why these datasets ship as IDs and timestamps

    The videos belong to their uploaders. Dataset authors can license their annotations (captions, labels, clip boundaries), but they generally can't redistribute the footage. What you download is a table of pointers, so every team that wants the pixels has to collect them again. This has three practical effects: your copy of the dataset is smaller than the paper's, it shrinks over time, and it depends on how well your downloader keeps up with YouTube.

    Dataset overview

    All numbers below come from each dataset's paper, project page or official repo. Durations appear only where the source states them.

    Dataset (year) What's released Source videos Clips / segments Stated duration Annotation license Official tooling
    Panda-70M (2024) CSV with YouTube URL, clip timestamps, caption, matching score; desirability and shot-boundary annotations added Oct 2024 3,779,763 (full train) 70,723,513 167k hrs (clips) Snap non-commercial research license video2dataset fork in snap-research/Panda-70M
    HD-VILA-100M (2022) Video URLs plus clip spans, separate metadata archive; text is ASR transcripts 3.3M 100M (avg 13.4 s) 371.5k hrs, 720p Research Use of Data Agreement (R-UDA) v1.0 src/cut_videos.py in microsoft/XPretrain (cutting only)
    InternVid (2023) YouTube IDs, clip timestamps, generated captions; subsets 10M-FLT, 10M-DIV, 18M-AES; full annotations as InternVid-Full 7M+ 234M (paper); 10M in -FLT ~760k hrs (full) CC BY-NC-SA 4.0 (Hugging Face card, gated) None for video download in the README
    HowTo100M (2019) YouTube IDs; captions are narrations from YouTube's automatic subtitles; precomputed S3D features 1.22M 136M "15 years of video" (project page) Not stated on the project page Feature-extraction code in antoine77340/howto100m
    Kinetics-700 / -2020 YouTube ID, ~10 s segment, action label. CVDF also hosts the video files One clip per video 647,907 (2020 report: 545,793 / 34,256 / 67,858) Not stated Not verified here; check DeepMind's Kinetics page cvdfoundation/kinetics-dataset (k700_2020_downloader.sh)
    AudioSet (2017) CSV: YTID, start, end, labels; 128-d audio features (TFRecord) One segment per video 2,085,544 ten-second segments, 527 classes Not stated (≈5,793 hrs at 10 s each) CC BY 4.0 (ontology CC BY-SA 4.0) None for raw audio
    VoxCeleb2 (2018) YouTube URLs and utterance timestamps. The site now says URLs, audio and video are "no longer available from this website" 150,480 1,128,246 utterances, 6,112 speakers Not stated Metadata CC BY-SA 4.0 None currently

    YT-Temporal-180M (6 million videos, 180M extracted frames) and YT-Temporal-1B (MERLOT Reserve, 20 million videos) aren't in the table. The MERLOT repo still lists "information about the videos used in this work" as planned, and I found no public ID list for either. Ask the authors directly.

    HD-VILA's numbers are consistent with each other: 100M clips × 13.4 s ≈ 372k hours. That means the 371.5k hours figure counts clip duration, not full source-video duration.

    Step 1: get the metadata

    • Panda-70M: CSVs are linked from the dataset_dataloading README. Training sets are zipped (full: 2.73 GB; 10M: 504 MB; 2M: 118 MB). The 10M subset keeps at most 3 clips per source video, and every clip has a matching score above 0.43.
    • HD-VILA-100M: the README links a URL archive and a metadata archive on Azure Blob, last updated August 2026. Each JSONL record has video_id, url and a clip list of {clip_id, span: [start, end]}.
    • InternVid: OpenGVLab/InternVid on Hugging Face is gated. You fill in name, organization and email and accept the terms before the JSONL files become available.
    • AudioSet: three CSVs (eval 20,383, balanced train 22,176, unbalanced train 2,042,985 segments).

    Step 2: choose your downloader

    Panda-70M: the official video2dataset fork

    Panda-70M's README says to use the bundled fork, because "the video2dataset from the official repository cannot work with our csv format":

    git clone https://github.com/snap-research/Panda-70M.git
    cd Panda-70M/dataset_dataloading/video2dataset
    pip install -e .
    cd ..
    
    video2dataset --url_list="panda70m_training_2m.csv" \
                  --url_col="url" \
                  --caption_col="caption" \
                  --clip_col="timestamp" \
                  --output_folder="panda70m_2m" \
                  --save_additional_columns="[matching_score,desirable_filtering,shot_boundary_detection]" \
                  --config="video2dataset/video2dataset/configs/panda70m.yaml"

    The bundled panda70m.yaml sets download_size: 360 and download_audio: True, downloads English subtitles, and writes 100 samples per shard using 32 processes × 32 threads. The README's known-issues table maps HTTP 403 to "your IP got blocked" and HTTP 429 to "reduce processes_count and thread_count". It also notes that several YouTube errors are fixed by upgrading yt-dlp, so run pip install -U "yt-dlp[default]" inside that environment after installing. Current yt-dlp needs Python 3.11+, plus yt-dlp-ejs and a JavaScript runtime such as Deno for full YouTube support (see the yt-dlp README).

    Upstream video2dataset

    iejMac/video2dataset is still at that address (MIT license, not archived). Its last push was in July 2024, and the latest PyPI release is 1.3.0 from February 2024, so treat it as stable but largely unmaintained. It reads CSV or Parquet (--input_format), writes files, webdataset, parquet or tfrecord (--output_format), and distributes work over multiprocessing, PySpark or Slurm. The README says PySpark mode was used to download 40M videos. It's a reasonable base for InternVid or HD-VILA, but you'll have to convert their timestamp formats to whatever clip_col format your version expects. Test on 1,000 rows before launching a full run.

    yt-dlp directly

    For custom pipelines, call yt-dlp directly. How you handle clipping depends on how many clips come from each source video:

    • Many clips per video (Panda-70M averages ~18.7 clips per source video in the full set; HD-VILA has ~30): download the whole video once, then cut it locally.
    • One short segment per video (AudioSet, Kinetics): download just the section with --download-sections. The * prefix marks a time range, and the option needs ffmpeg.
    # Full video, capped at 360p, smallest file among the remaining candidates
    yt-dlp -S "res:360,+size" --merge-output-format mp4 \
      -o "videos/%(id)s.%(ext)s" \
      "https://www.youtube.com/watch?v=QMi8x8o55Ns"
    
    # One 10-second AudioSet segment, audio only
    yt-dlp -f bestaudio -x --audio-format flac \
      --download-sections "*420-430" \
      -o "audio/%(id)s_%(section_start)s.%(ext)s" \
      "https://www.youtube.com/watch?v=-0RWZT-miFs"

    Section downloads cut at keyframes by default. Add --force-keyframes-at-cuts for accurate boundaries. The README warns that this is slow because it re-encodes.

    Cutting clips with ffmpeg

    HD-VILA's cut_videos.py uses ffmpeg -ss … -t … -c copy. Stream copy is fast, but each clip starts at the nearest keyframe, so a 5-second clip can pick up extra frames. If your training needs exact caption alignment, re-encode:

    import json
    import subprocess
    from pathlib import Path
    
    def to_sec(ts):  # "00:04:52.140" to 292.14
        h, m, s = ts.split(":")
        return int(h) * 3600 + int(m) * 60 + float(s)
    
    with open("meta_part0.jsonl") as f:  # one HD-VILA metadata part
        for line in f:
            meta = json.loads(line)
            src = Path("videos") / f"{meta['video_id']}.mp4"
            if not src.exists():
                continue
            out_dir = Path("clips") / meta["video_id"]
            out_dir.mkdir(parents=True, exist_ok=True)
            for c in meta["clip"]:
                start, end = (to_sec(t) for t in c["span"])
                subprocess.run([
                    "ffmpeg", "-y", "-loglevel", "error",
                    "-ss", f"{start:.3f}", "-i", str(src), "-t", f"{end - start:.3f}",
                    "-c:v", "libx264", "-preset", "veryfast", "-crf", "23",
                    "-c:a", "aac", "-b:a", "96k",
                    str(out_dir / c["clip_id"]),
                ], check=True)

    Choosing format and resolution

    Resolution has more effect on storage and bandwidth than any other setting. In yt-dlp's format sorting, res:360 prefers the largest format that is no larger than 360p. Adding +size breaks ties toward smaller files. Panda-70M's tooling defaults to 360p for this reason. If you're training generation models that need HD, set a higher download_size and budget for it. Use audio-only formats for AudioSet and VoxCeleb-style work. Also consider whether you need audio at all: Panda's README notes that turning off download_audio speeds up downloads.

    Link rot: plan for missing videos

    Videos get deleted, set to private, region-locked or taken down by their owners. CVDF's Kinetics-700-2020 readme is one of the few published measurements. It was downloaded in September 2020 with youtube-dl:

    • Train: 536,699 of 545,793 videos (98.3%)
    • Validation: 33,966 of 34,256 (99.2%)
    • Test: 64,535 of 67,858 (95.1%)

    About 7,000 videos that were unavailable by September 2020 were recovered from a December 2019 crawl. Expect losses to grow the longer a dataset has been public. Panda-70M's README lists "set to private or removed" as an expected failure and tells you to skip those samples. VoxCeleb2's official site no longer provides the URL list at all. In practice, always report how many clips you actually obtained, and don't assume your numbers are directly comparable to the paper's.

    Estimating storage

    Formula: bytes = hours × 3600 × bitrate (bits/s) ÷ 8.

    Panda-70M gives a calibration point: ~36 TB for 167k hours at 360p with audio. That's 36×10¹² B ÷ (167,000 × 3600 s) ≈ 59,900 B/s ≈ 0.48 Mbps. The table below assumes 0.5 Mbps for 360p and 2.5 Mbps for 720p H.264. These are round assumptions, not measurements.

    Dataset (stated hours) Seconds At 0.5 Mbps At 2.5 Mbps
    Panda-70M full (167k, clips) 6.01×10⁸ ≈ 37.6 TB ≈ 188 TB
    HD-VILA-100M (371.5k, clips) 1.34×10⁹ ≈ 83.6 TB ≈ 418 TB
    InternVid full (~760k, source videos) 2.74×10⁹ ≈ 171 TB ≈ 855 TB

    These estimates cover the clips you store. Transfer volume is higher if you download full source videos and keep only the clipped parts, and the sources don't state total source duration for Panda-70M or HD-VILA. For transfer time: 37.6 TB × 8 ÷ 1 Gbps ≈ 300,800 s ≈ 3.5 days at full line rate, which you won't sustain in practice. Bandwidth for large-scale video downloads goes through the full calculation.

    Output formats

    • Loose files (video2dataset's default files): Panda-70M's layout is one folder per shard, with .mp4, .txt (caption) and .json (metadata, including status) per sample. This is easy to inspect but slow on object storage once you have millions of files.
    • WebDataset tar shards (--output_format webdataset): this is what the video2dataset README recommends "for most use cases". It streams well from S3/GCS into PyTorch, TF or JAX.
    • Parquet metadata: video2dataset writes a Parquet file per shard alongside the tars. Merge these into a single manifest (clip ID, video ID, start, end, caption, status, error, bytes, resolution, sha256, download date) so you can filter and audit without opening any shards.

    Panda's README also warns that shards are unshuffled, so all clips from one source video end up in the same shard. Shuffle before training.

    Resumability and failure tracking

    video2dataset's incremental_mode (default incremental) skips finished shards on restart. This only works if you keep the same output folder, the same number_sample_per_shard and the same input list. Failed samples record "status": "failed_to_download" and an error_message in their JSON.

    With plain yt-dlp, --download-archive FILE skips IDs that have already been downloaded. You still need to separate permanent failures from transient ones. A minimal status log:

    import json
    import subprocess
    from pathlib import Path
    
    IDS = Path("video_ids.txt")
    LOG = Path("status.jsonl")
    OUT = Path("videos")
    PERMANENT = ("Video unavailable", "Private video", "removed by the uploader")
    
    finished = set()
    if LOG.exists():
        with LOG.open() as f:
            for line in f:
                rec = json.loads(line)
                if rec["status"] in ("ok", "gone"):
                    finished.add(rec["id"])
    
    OUT.mkdir(exist_ok=True)
    with LOG.open("a") as log:
        for vid in IDS.read_text().split():
            if vid in finished:
                continue
            p = subprocess.run(
                ["yt-dlp", "--no-progress", "-S", "res:360,+size",
                 "--merge-output-format", "mp4",
                 "-o", str(OUT / "%(id)s.%(ext)s"),
                 f"https://www.youtube.com/watch?v={vid}"],
                capture_output=True, text=True,
            )
            if p.returncode == 0:
                status = "ok"
            elif any(s in p.stderr for s in PERMANENT):
                status = "gone"
            else:
                status = "retry"  # rate limits, bot checks, network errors
            log.write(json.dumps({"id": vid, "status": status,
                                  "error": p.stderr.strip()[-500:]}) + "\n")
            log.flush()

    On the next run, IDs marked retry are attempted again, while ok and gone are skipped. YouTube's error strings change over time, so check a sample of retry errors periodically and update the matcher.

    Scaling: rate limits and bot checks

    At 10k+ videos, throughput is limited by YouTube's limits rather than your code. Typical failures are HTTP 429, 403 and "Sign in to confirm you're not a bot". Things that help:

    For the end-to-end pipeline after download (dedup, filtering, captioning), see building a YouTube video dataset for multimodal training.

    Licensing and compliance

    The licenses above (Snap's non-commercial license, R-UDA, CC BY-NC-SA 4.0, CC BY 4.0) cover the annotations. They grant no rights to the underlying videos, which belong to their uploaders. YouTube's Terms of Service restrict automated access and downloading. Copyright and licensing still apply, and some teams filter for Creative Commons-licensed videos. Faces and voices raise privacy issues (e.g., GDPR), and takedown and opt-out requests should be honored. Panda-70M's authors, for example, offer removal on request. Get legal review for your jurisdiction and use case before collecting or redistributing.

    If you'd rather not run this yourself

    Corpora offers two options. If you run your own stack, dedicated 1–40 Gbps unmetered proxy capacity (HTTP(S)/SOCKS5, compatible with yt-dlp) is $2,000 per Gbps per month. Alternatively, a fully managed collection service delivers video, audio, subtitles and metadata (JSONL/Parquet with sha256 checksums and manifests) to your S3, GCS or Azure bucket, priced per delivered PB, with a free sample batch. Contact us to scope it.

    FAQ

    Can I download Panda-70M videos directly instead of from YouTube?

    Not officially. The release consists of CSVs (URLs, timestamps, captions, scores) and code. The videos have to be fetched from YouTube with the bundled video2dataset fork or your own tooling.

    How big is Panda-70M once downloaded?

    The README lists ~36 TB for the full training set (70.7M clips, 167k hours), ~8.0 TB for the 10M subset and ~1.6 TB for the 2M subset, at the default 360p with audio.

    Can I use HD-VILA-100M or InternVid commercially?

    Their annotation licenses don't allow it: R-UDA v1.0 is limited to non-commercial research, and InternVid is CC BY-NC-SA 4.0. Separately, neither license covers rights to the videos themselves.

    Why do I get fewer clips than the paper reports?

    Link rot. Videos get removed or set to private over time. Kinetics-700-2020's own download already missed about 2% within months of release. Log every failure so you can report what you actually obtained.

    Should I use video2dataset or yt-dlp?

    Use the official fork for Panda-70M. Elsewhere, video2dataset gives you sharding, WebDataset/Parquet output and resumption. Plain yt-dlp gives you more control, but you write the bookkeeping yourself.

  • Bandwidth for Large-Scale Video Downloads: 100 TB to 5 PB/Month

    If you need to collect 100 TB, 1 PB or 5 PB of video or audio per month, the first question is not which tool to use. It is how many gigabits per second you need to keep running all month. This guide gives you the exact formula, sizing tables for 10 TB to 5 PB per month, the reverse table (what 1–40 Gbps can actually move), and the practical limits that decide whether you hit those numbers.

    TL;DR

    • Formula: Gbps = TB/month × 8,000 ÷ seconds per month. With a 30-day month (2,592,000 s), 1 Gbps at 100% utilization moves 324 TB/month, or 10.8 TB/day.
    • 100 TB/month ≈ 0.31 Gbps, 1 PB/month ≈ 3.09 Gbps, 5 PB/month ≈ 15.4 Gbps. That assumes the link is saturated 24/7, and it won't be.
    • Plan for 60–70% sustained utilization. That means provisioning about 1.4–1.7× the theoretical number.
    • 800 TB/month ≈ 2.47 Gbps at 100%, so provision about 4–5 Gbps.
    • Writing data into S3 or GCS from the internet is free. Reading it back out to the internet is not.
    • Per-GB pricing only makes sense at low volume. At $2,000 per Gbps per month, the break-even is $2,000 ÷ (price per GB) GB per month.

    The formula

    Bandwidth is measured in bits per second, but storage is measured in bytes. You convert between them, then divide by the number of seconds in the period:

    Gbps = TB_per_month × 8,000 / seconds_per_month
    
    # 1 TB (decimal) = 10^12 bytes = 8 × 10^12 bits = 8,000 Gb
    # 30-day month    = 30 × 86,400   = 2,592,000 s
    # average month   = 30.44 × 86,400 ≈ 2,630,016 s  (365.25 / 12 days)

    This article uses the 30-day month. It is the more conservative choice, because fewer seconds means a slightly higher Gbps figure. The 30.44-day average lowers every result by about 1.5% (100 TB/month comes out at 0.304 Gbps instead of 0.309). For capacity planning that difference is smaller than your error bars.

    TB vs TiB

    Network speeds are always decimal: 1 Gbps = 10^9 bits per second. Storage tools often report binary units even when they label them "TB". Check whether df -h, your object-store console or your billing export means TB or TiB:

    • 1 TB = 10^12 bytes; 1 TiB = 2^40 bytes ≈ 1.0995 TB (about 10% larger).
    • 1 PB = 10^15 bytes; 1 PiB = 2^50 bytes ≈ 1.126 PB (about 12.6% larger).
    • 100 TiB/month needs 0.339 Gbps, compared with 0.309 Gbps for 100 TB. 1 PiB/month needs 3.47 Gbps, compared with 3.09 Gbps for 1 PB.

    If your target is set in TiB or PiB, convert it to decimal before you use the tables below.

    Monthly volume → required bandwidth

    The "100%" column is pure arithmetic. The provisioned columns divide it by 0.70 and 0.60, which are the sustained utilization levels you can realistically plan around (the next section explains why). All values use a 30-day month and decimal units.

    Volume / month Per day Gbps at 100% Provision at 70% util. Provision at 60% util.
    10 TB 0.33 TB 0.031 0.04 Gbps 0.05 Gbps
    50 TB 1.67 TB 0.154 0.22 Gbps 0.26 Gbps
    100 TB 3.33 TB 0.309 0.44 Gbps 0.51 Gbps
    250 TB 8.33 TB 0.772 1.10 Gbps 1.29 Gbps
    500 TB 16.7 TB 1.543 2.20 Gbps 2.57 Gbps
    800 TB 26.7 TB 2.469 3.53 Gbps 4.12 Gbps
    1 PB 33.3 TB 3.086 4.41 Gbps 5.14 Gbps
    2 PB 66.7 TB 6.173 8.82 Gbps 10.29 Gbps
    5 PB 166.7 TB 15.432 22.05 Gbps 25.72 Gbps

    Round up to whatever increments your provider sells. For example, 1 PB/month lands at 5 Gbps provisioned, 2 PB/month at 10 Gbps, and 5 PB/month at 25 Gbps.

    Why plan for 60–70% utilization, not 100%

    The 100% number assumes every second of the month moves useful bytes at line rate. In practice, several things get in the way:

    • Protocol overhead. Ethernet, IP and TCP headers, TLS records and HTTP framing all use bandwidth on the wire but don't count toward the bytes stored on disk. Proxy hops add their own overhead on top.
    • Retries and failed or partial downloads. A download that dies at 80% and restarts from zero (or re-fetches fragments) still used bandwidth. Expired stream URLs, bot checks (see fixing "Sign in to confirm you're not a bot") and connection resets all show up as wasted bytes.
    • Non-media traffic. Metadata extraction, manifest and player requests, subtitle fetches and format probing all use requests and time without adding much volume.
    • Per-connection throughput limits. A single stream rarely fills a fast link. Unless you keep enough downloads in flight at all times, the pipe sits partly idle.
    • Diurnal and operational gaps. Queue refills, deploys, worker restarts, rate-limit backoff, proxy rotation and the daily traffic patterns of shared upstreams all create troughs. A 24-hour average is always below the peak.

    Sustained 60–70% utilization is a reasonable target for a well-run pipeline. It is a planning assumption, not a measured guarantee. If your own monitoring shows 80% sustained, you can provision closer to the 100% column. If you are running at 40%, fix the pipeline before you buy more bandwidth.

    Worked example: 800 TB per month

    800 × 8,000 / 2,592,000 = 2.469 Gbps   (at 100% utilization, 30-day month)
    800 × 8,000 / 2,630,016 = 2.433 Gbps   (30.44-day average month)
    
    2.469 / 0.70 = 3.53 Gbps
    2.469 / 0.60 = 4.12 Gbps
    → provision ~4–5 Gbps
    
    At 4 Gbps provisioned, 800 TB needs 2.469 / 4 = 62% sustained utilization.
    At 5 Gbps provisioned, it needs 2.469 / 5 = 49%, which leaves room for re-downloads and growth.

    Bandwidth → monthly volume (reverse table)

    This table answers the opposite question: you already have a link, so how much can it move? The formula is TB = Gbps × seconds / 8,000.

    Link Per day at 100% Per 30-day month at 100% Per 30.44-day month at 100% Per 30-day month at 65%
    1 Gbps 10.8 TB 324 TB 329 TB 211 TB
    2 Gbps 21.6 TB 648 TB 658 TB 421 TB
    5 Gbps 54 TB 1.62 PB 1.64 PB 1.05 PB
    10 Gbps 108 TB 3.24 PB 3.29 PB 2.11 PB
    20 Gbps 216 TB 6.48 PB 6.58 PB 4.21 PB
    40 Gbps 432 TB 12.96 PB 13.15 PB 8.42 PB

    A useful rule of thumb: 1 Gbps sustained ≈ 10.8 TB/day ≈ 324 TB per 30 days. Everything else scales linearly from there.

    What that volume means in hours of media

    Training-data budgets are usually set in hours, not terabytes. To convert between them you need an average bitrate:

    GB per hour      = Mbps × 3,600 / 8 / 1,000  = Mbps × 0.45
    hours per PB     = 1,000,000 / (GB per hour)

    Delivered bitrates on YouTube vary a lot with codec (H.264/AVC, VP9, AV1), frame rate and content. Static talking heads compress far better than sports. The figures below are approximate planning assumptions, not published specifications. For reference, YouTube's recommended upload encoding settings list 8 Mbps for 1080p SDR at 24–30 fps and 12 Mbps at 48–60 fps. Those are upload targets; the re-encoded streams YouTube delivers are usually well below them.

    Format (approximate) Assumed bitrate GB per hour Hours per 100 TB Hours per 1 PB
    Audio only, AAC (m4a) ~128 kbps 0.128 Mbps 0.058 ~1.74 M ~17.4 M
    Audio only, Opus (webm) ~160 kbps 0.16 Mbps 0.072 ~1.39 M ~13.9 M
    720p video + audio 1.5 Mbps 0.675 ~148 k ~1.48 M
    1080p, efficient codec (VP9/AV1) 2 Mbps 0.9 ~111 k ~1.11 M
    1080p, H.264 or high-motion 4 Mbps 1.8 ~56 k ~556 k
    2160p (4K) 15 Mbps 6.75 ~15 k ~148 k

    The format choice can change your bandwidth bill by an order of magnitude. Audio-only collection for speech models needs roughly 1/12th to 1/30th of the bandwidth of 1080p video for the same number of hours. Before you size anything, measure your actual mix on a sample with yt-dlp. -O/--print implies simulate mode, so nothing is downloaded:

    yt-dlp -f "bv*[height<=1080]+ba/b[height<=1080]" \
      -O "%(id)s %(duration)s %(tbr)s %(filesize,filesize_approx)s" \
      --batch-file sample_urls.txt > sample_sizes.txt

    tbr is the total average bitrate in kbps, and filesize or filesize_approx is in bytes (it may print NA when unknown). Use -F to see every available format for a single video. Multiply the sample's mean bytes per second of duration by your target hours to get TB, then use the first table. For format selection and proxy flags, see the yt-dlp proxy guide.

    Storage, ingress and egress costs

    • Ingress is free on the big clouds. The Amazon S3 pricing page lists "data transferred in from the internet" among the transfers you don't pay for. Google's Cloud Storage pricing lists inbound data transfer as free. You still pay per-request charges (PUT/POST, multipart parts), so avoid uploading millions of tiny objects.
    • Egress is charged. S3 includes 100 GB/month of internet egress free, aggregated across AWS services, and charges per GB beyond that. For GCS general network usage, internet egress starts at $0.12/GiB for the first 10 TiB to worldwide destinations (excluding Asia and Australia) and steps down at volume. Moving a petabyte out of a cloud to train elsewhere costs a lot, so decide early where training will run.
    • Storage grows linearly. At 1 PB/month you add 12 PB a year before replicas. Multiply by your per-GB-month rate and storage class, and budget for lifecycle rules (cold tiers for raw downloads, hot tiers for curated shards).
    • Downloading on a cloud VM means your proxy or provider traffic arrives as VM ingress, which is free on AWS and GCP. But the traffic between your VM and an external proxy is outbound requests plus inbound responses, so check your provider's NAT gateway and inter-zone charges too.

    Per-GB vs per-Gbps pricing: break-even

    Proxy and transit capacity is sold in one of two ways: metered (per GB transferred) or flat (per Gbps of committed capacity, unmetered). Using $2,000 per Gbps per month as the flat price, the break-even is:

    break_even_GB_per_month = flat_price_per_Gbps / price_per_GB
                            = 2,000 / price_per_GB
    
    1 Gbps can move at most 324,000 GB per 30 days (~210,000 GB at 65% utilization).

    The per-GB prices below are illustrative examples, not quotes from any provider:

    Example per-GB price Break-even volume per Gbps Cost of 100 TB metered Cost of 1 PB metered
    $0.10 / GB 20 TB/month $10,000 $100,000
    $0.50 / GB 4 TB/month $50,000 $500,000
    $1.00 / GB 2 TB/month $100,000 $1,000,000
    $3.00 / GB 0.67 TB/month $300,000 $3,000,000

    Even at $0.10/GB, metered pricing costs more than a flat 1 Gbps line once you move more than 20 TB/month, which is about 6% of that line's capacity. At the 100 TB to 5 PB volumes in this article, per-GB pricing is only reasonable for small, hard-to-reach slices of traffic. It doesn't work for the bulk of the download volume.

    Practical limits that decide whether you hit the number

    Parallelism

    Required concurrency follows directly from per-connection throughput:

    concurrent_downloads = target_Gbps × 1,000 / per_connection_Mbps
    
    e.g. 2.5 Gbps at 20 Mbps per download  → 125 downloads in flight
         15 Gbps  at 20 Mbps per download  → 750 downloads in flight

    The 20 Mbps figure is only an example. Measure your own per-download throughput through your proxies, because server-side pacing, proxy hops and round-trip time all cap a single stream. TCP itself sets a ceiling of window ÷ RTT: a 64 KiB window at 100 ms RTT tops out around 5.2 Mbps. yt-dlp's -N/--concurrent-fragments parallelizes fragments within one DASH/HLS download, but at petabyte scale most of the parallelism comes from running many independent downloads across many workers and IPs.

    Disk write throughput

    Divide Gbps by 8 to get the sustained write rate: 2.5 Gbps ≈ 312 MB/s, 15.4 Gbps ≈ 1.93 GB/s, and that is around the clock. If you download separate video and audio streams and merge them with ffmpeg locally, every byte is written at least twice and read once. Plan for roughly 2–3× the network rate in disk I/O, or stream finished files directly to object storage and keep only scratch space local.

    NIC and host limits

    A 10 GbE NIC can't carry 10 Gbps of useful payload once you subtract overhead. TLS termination, Python processes per download, ffmpeg remuxing and checksum computation all use CPU. File descriptors, ephemeral ports and conntrack tables run out at thousands of concurrent connections, so raise ulimit -n and check kernel limits before blaming the network.

    Why one fast server is not enough

    A single 25 or 40 Gbps box is a single point of failure, a single egress IP set (which gets rate-limited or challenged as one unit), and a single disk and CPU bottleneck. Horizontal scaling works better: many workers of a few Gbps each, pulling from a shared queue, uploading to object storage, and spread across regions and IP pools. If one node dies, you lose a fraction of throughput instead of the whole month's schedule.

    Monitor utilization, not just "is it running"

    • Track bytes stored per hour against the target rate (for 1 PB/month that is about 1.39 TB/hour), not just link utilization. The gap between the two is your waste.
    • Graph link utilization per worker and in aggregate, and alert on sustained drops below your planning assumption.
    • Track the retry rate, failure reasons (HTTP 403/429, bot checks, timeouts) and the bytes discarded from partial downloads.
    • Watch the queue depth so workers never idle waiting for URLs.

    Compliance

    YouTube's Terms of Service restrict automated access and downloading. Copyright and licensing matter: many teams filter for Creative Commons-licensed videos. Privacy also matters, since faces and voices can be personal data under GDPR, and so does honoring takedown and opt-out requests. Get legal review for your jurisdiction and use case before you collect at scale. For dataset design, see building a YouTube video dataset for multimodal training and downloading Panda-70M, HD-VILA and InternVid.

    If you'd rather not run this yourself

    Corpora sells dedicated 1–40 Gbps unmetered proxy capacity (HTTP(S) and SOCKS5, compatible with yt-dlp and custom crawlers) at $2,000 per Gbps per month, with volume discounts from 5 Gbps. See pricing. If you only want the data, the fully managed option delivers deduplicated video, audio, subtitles and metadata straight to your S3, GCS or Azure bucket, priced per delivered PB. Get in touch for a free sample batch.

    FAQ

    How many Gbps do I need for 1 PB per month?

    1 PB × 8,000 ÷ 2,592,000 s ≈ 3.09 Gbps at 100% utilization over a 30-day month. At a realistic 60–70% sustained utilization, provision about 4.4–5.1 Gbps. In practice that means 5 Gbps.

    How much data can a 1 Gbps connection download in a month?

    About 324 TB in 30 days at 100% utilization (10.8 TB/day), or about 329 TB over an average 30.44-day month. At 65% sustained utilization, expect about 210 TB.

    How much bandwidth do I need for 100 TB per month?

    About 0.31 Gbps sustained. At 60–70% utilization, provision 0.44–0.51 Gbps, so a 1 Gbps line has plenty of headroom.

    Does uploading to S3 or Google Cloud Storage cost bandwidth fees?

    No. Data transfer in from the internet is free on both S3 and GCS according to their pricing pages. Requests and storage are billed, and so is data transfer out to the internet.

    Should I size in TB or TiB?

    Use decimal TB to match network units (Gbps is decimal). If your storage reports TiB, multiply by 1.0995 before you use the formula. A 100 TiB target needs about 10% more bandwidth than 100 TB.