hi, i'm dexter
  • about me
  • audio
  • video
  • articles
  • say hi
what up
/
software
software

software

  • inDEXdown
  • get it here:
  • version for power users:
  • usage examples
  • what about a windows version?

inDEXdown

inDEXdown downloads any video, and creates a source index.

image

inDEXdown lets you download YouTube, Instagram, Twitter, or TikTok videos with one click.

You can also try it to download things from other sites:

  • audio platforms like Soundcloud (it will download this as mp3), or
  • even non-social media sites like CNN or CNBC

It provides you with all the important information in an easy-to-use history pane:

image

It also puts the most important data right in the file name, so you won’t lose it.

image

more exhaustive information is saved into a spreadsheet log file you can reference when making credits, including

  • date you downloaded the video
  • date the video was published
  • username (eg @ABC7)
  • display name (eg ‘ABC Channel 7 News’)
  • title and original URL of video

This is built for journalists or video editors who need to:

  • download source/archival from social media sites
  • build a reference library without extra bookkeeping

get it here:

download removed for now. back soon.

version for power users:

inDEXdown is a wrapper around yt-dlp and ffmpeg. So, to be clear - the GUI is vibe-coded, but the open-source code under the hood is solid. The unique part isn't the downloading itself, but the conventions:

  • informative, readable filenames
  • a CSV log of everything downloaded, for attribution and reuse

That's mostly configuration, but it's the part people tend not to set up.

If you're comfortable in a terminal and would rather not download anything from me, you can get the same functionality with a single Python script. This is cross-platform, and works on macOS, Windows, and Linux.

inDEXdown.py9.6 KiB

you can see how to use it below:

‣

usage examples

what it does

  • Names file: @username - title [video_id] (YYYY-MM-DD).mp4
  • Logs to inDEXdown_log.csv with full attribution info

how to use it

it’s pretty simple:

python3 inDEXdown.py "https://youtube.com/watch?v=xxx"

Don't forget the quotes!

by default, it downloads H.264 video. This looks good and is widely compatible.

To get the absolute best quality available, add --highest:

python3 inDEXdown.py "https://youtube.com/watch?v=xxx" --highest

(note: highest quality may be AV1 on YouTube, which won't play in QuickTime. Use VLC to watch.)

output

by default, the videos and the CSV log are saved to:

  • macOS: ~/Movies/inDEXdown/
  • Windows/Linux: ~/Videos/inDEXdown/

Or wherever you specify with --output.

about that last bit: to save to a custom directory, add --output:

python3 inDEXdown.py "https://youtube.com/watch?v=xxx" --output ~/Downloads

note that this might break your csv logfile if you download to different folders. If you care about having a consistent log, I’d recommend choosing one place for your downloads to go, and sticking with it.

if you have any suggestions for improvement, let me know!

‣
(want to preview the code? expand here)

Tweak as you see fit.

what about a windows version?

no plans for that now, but you can try the ‘power users’ script above.

It’ll run in the Command Prompt, but the functionality is the same.

‣
Expand for Windows-specific instructions:
  1. Install Python from python.org (check "Add to PATH" during install)
  2. Open Command Prompt and copy-paste in the following and hit Enter
 pip install yt-dlp
  1. Install ffmpeg: download the "release full" build from gyan.dev/ffmpeg/builds, extract the zip, and copy ffmpeg.exe from the bin folder to the same folder as inDEXdown.py
  2. Download the above inDEXdown.py to a folder (like your Desktop)
  3. In Command Prompt, run it like this:
cd Desktop

this will make sure you’re running in the Desktop folder (or wherever you put the file).

  1. Then run it like so:
python inDEXdown.py "https://youtube.com/watch?v=xxx"

What you get:

Every video you download gets logged to a file called inDEXdown_log.csv in your output folder. This is a spreadsheet with the download date, username, video title, original URL, and local filename - everything you need for attribution.

You can open it in Excel, Google Sheets, or any spreadsheet app. You may want to right-click the file and choose "Open with" > Excel. Double-clicking it might open in Notepad, which works but isn't as pretty.

Your videos and the CSV log are saved to: C:\Users\YourName\Videos\inDEXdown\

anyway yeah have fun. any questions, feel free to hit me up.

…

…..

#!/usr/bin/env python3
# inDEXdown (power user edition) by @dexdigi - whatupdex.com
# Downloads videos with source attribution logging. Run with --help for usage.

import subprocess
import sys
import os
import csv
import re
import json
from datetime import datetime
from pathlib import Path

def get_output_dir():
    home = Path.home()
    if sys.platform == "darwin":
        d = home / "Movies" / "inDEXdown"
    else:
        d = home / "Videos" / "inDEXdown"
    d.mkdir(parents=True, exist_ok=True)
    return d

def detect_platform(url):
    if "tiktok.com" in url:
        return "tiktok"
    if "instagram.com" in url:
        return "instagram"
    if "twitter.com" in url or "x.com" in url:
        return "twitter"
    if "youtube.com" in url or "youtu.be" in url:
        return "youtube"
    return "other"

def extract_instagram_handle(url):
    try:
        from urllib.parse import urlparse
        parsed = urlparse(url)
        parts = [p for p in parsed.path.split("/") if p]
        if len(parts) >= 2 and parts[1] in ("p", "reel", "tv"):
            return parts[0]
    except Exception:
        pass
    return None

def sanitize_handle(handle):
    return re.sub(r"[^a-zA-Z0-9._-]", "", handle)

def sanitize_title(title, max_len=30):
    safe = re.sub(r"[^a-zA-Z0-9 .,!?'()-]", "", title[:max_len])
    safe = re.sub(r"  +", " ", safe).strip()
    return safe if safe else "video"

def extract_names(platform, url, meta):
    uploader_id = meta.get("uploader_id") or ""
    channel = meta.get("channel") or ""
    uploader = meta.get("uploader") or ""
    video_id = meta.get("id", "unknown")
    title = meta.get("title", "video")

    if platform == "tiktok":
        handle = uploader or video_id
        display_name = channel or handle
    elif platform == "instagram":
        ig_handle = extract_instagram_handle(url)
        handle = ig_handle or channel or video_id
        display_name = uploader or handle
    elif platform == "twitter":
        handle = uploader_id or video_id
        if handle.startswith("@"):
            handle = handle[1:]
        display_name = uploader or handle
        prefix = f"{display_name} - "
        if title.startswith(prefix):
            title = title[len(prefix):]
    else:  # youtube, other
        if uploader_id and uploader_id.startswith("@"):
            handle = uploader_id[1:]
        elif channel and channel != "NA":
            handle = channel
        elif uploader and uploader != "NA":
            handle = uploader
        else:
            handle = video_id
        display_name = uploader or handle

    if not handle or handle == "NA":
        handle = video_id
    if not display_name or display_name == "NA":
        display_name = handle

    handle = sanitize_handle(handle)
    if not handle:
        handle = video_id

    meta["title"] = title
    return handle, display_name

def build_filename(handle, title, video_id, upload_date, ext="mp4"):
    safe_title = sanitize_title(title)
    return f"@{handle} - {safe_title} [{video_id}] ({upload_date}).{ext}"

def neutralize_csv(value):
    if value and value[0] in "=+-@":
        return "'" + value
    return value

def log_to_csv(output_dir, handle, display_name, title, url, upload_date, video_id, filename):
    csv_path = output_dir / "inDEXdown_log.csv"
    header = ["download_date", "username", "display_name", "title", "url", "upload_date", "video_id", "filename"]

    if csv_path.exists():
        content = csv_path.read_text(encoding="utf-8")
        if f",{video_id}," in content:
            print(f"  Already logged: {video_id}")
            return

    write_header = not csv_path.exists()

    with open(csv_path, "a", newline="", encoding="utf-8") as f:
        writer = csv.writer(f)
        if write_header:
            writer.writerow(header)
        writer.writerow([
            datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            f"@{handle}",
            neutralize_csv(display_name),
            neutralize_csv(title),
            url,
            upload_date,
            video_id,
            filename,
        ])

def check_actual_codec(filepath):
    try:
        result = subprocess.run(
            ["ffmpeg", "-i", filepath, "-hide_banner"],
            capture_output=True, text=True, timeout=10
        )
        return "Video: h264" in result.stderr
    except Exception:
        return False

def fetch_metadata(url):
    print("Fetching video info...")
    result = subprocess.run(
        ["yt-dlp", "--no-playlist", "--no-warnings", "--dump-single-json", url],
        capture_output=True, text=True, timeout=120
    )
    if result.returncode != 0:
        print(f"Error: couldn't get video info. Check the URL and try again.")
        sys.exit(1)
    return json.loads(result.stdout)

def download(url, quality="default", output_dir=None):
    if output_dir is None:
        output_dir = get_output_dir()
    else:
        output_dir = Path(output_dir).expanduser().resolve()
        output_dir.mkdir(parents=True, exist_ok=True)
    meta = fetch_metadata(url)

    video_id = meta.get("id", "unknown")
    title = meta.get("title", "video")
    upload_date_raw = meta.get("upload_date", "")
    upload_date = f"{upload_date_raw[:4]}-{upload_date_raw[4:6]}-{upload_date_raw[6:8]}" if len(upload_date_raw) == 8 else "unknown"
    duration = meta.get("duration", 0)
    vcodec = meta.get("vcodec", "unknown")

    platform = detect_platform(url)
    handle, display_name = extract_names(platform, url, meta)
    title = meta["title"]  # may have been modified by extract_names

    generic_prefixes = ["Video by ", "Photo by ", "Reel by ", "TikTok video #"]
    is_generic = any(title.startswith(p) for p in generic_prefixes) or (title.startswith("Video ") and len(title) < 12)
    if is_generic:
        desc = meta.get("description", "")
        if desc and desc != "NA":
            title = desc.replace("\n", " ")[:100]
        else:
            title = f"{handle} post"

    filename = build_filename(handle, title, video_id, upload_date)
    output_path = output_dir / filename

    is_audio_only = vcodec == "none"

    if is_audio_only:
        fmt = "bestaudio[ext=mp3]/bestaudio/best"
    elif quality == "highest":
        fmt = "bestvideo+bestaudio/best"
    else:
        fmt = "bestvideo[vcodec^=avc1]+bestaudio/bestvideo+bestaudio/best"

    if is_audio_only:
        filename = build_filename(handle, title, video_id, upload_date, ext="mp3")
        output_path = output_dir / filename

    output_template = str(output_path).replace(".mp4", ".%(ext)s").replace(".mp3", ".%(ext)s")

    print(f"Downloading: {title[:50]}")
    dl_cmd = [
        "yt-dlp",
        "--no-playlist", "--no-mtime", "--restrict-filenames",
        "--add-metadata", "--newline",
        "-f", fmt,
        "-o", output_template,
        url,
    ]

    if not is_audio_only:
        dl_cmd.insert(-1, "--merge-output-format")
        dl_cmd.insert(-1, "mp4")
        dl_cmd.insert(-1, "--postprocessor-args")
        dl_cmd.insert(-1, "ffmpeg:-c:v copy -c:a aac -b:a 192k -ac 2 -ar 48000")

    dl_result = subprocess.run(dl_cmd)

    if dl_result.returncode != 0:
        print("Download failed.")
        sys.exit(1)

    downloaded = None
    for f in output_dir.iterdir():
        if video_id in f.name and not f.name.endswith(".part"):
            downloaded = f
            break

    if not downloaded:
        print("Error: download seemed to succeed but can't find the file.")
        sys.exit(1)

    filepath = str(downloaded)

    if quality == "default" and downloaded.suffix in (".mp4", ".mkv", ".webm"):
        is_h264 = check_actual_codec(filepath)
        if not is_h264:
            print("Converting to h264...")
            tmp_output = filepath.replace(".mp4", "_h264.mp4")
            encode_result = subprocess.run([
                "ffmpeg",
                "-i", filepath,
                "-c:v", "libx264", "-preset", "fast", "-crf", "18",
                "-c:a", "aac", "-b:a", "192k", "-ac", "2", "-ar", "48000",
                "-y", tmp_output,
            ])
            if encode_result.returncode == 0:
                os.remove(filepath)
                os.rename(tmp_output, filepath)
            else:
                print("Warning: h264 conversion failed, keeping original.")
                try:
                    os.remove(tmp_output)
                except OSError:
                    pass

    log_to_csv(output_dir, handle, display_name, title, url, upload_date, video_id, downloaded.name)

    print(f"Done! Saved to {output_dir}")

def main():
    if len(sys.argv) < 2 or sys.argv[1] in ("-h", "--help"):
        print("inDEXdown (power user edition) by @dexdigi")
        print()
        print("Usage:")
        print("  python3 inDEXdown.py \"<url>\"                          Default quality (h264)")
        print("  python3 inDEXdown.py \"<url>\" --highest                Highest quality")
        print("  python3 inDEXdown.py \"<url>\" --output ~/Downloads     Custom output directory")
        print()
        print("Requirements: yt-dlp and ffmpeg must be installed and in your PATH.")
        print("  pip install yt-dlp")
        print("  ffmpeg: https://ffmpeg.org/download.html")

        sys.exit(0)

    url = sys.argv[1]
    quality = "highest" if "--highest" in sys.argv else "default"

    output_dir = None
    if "--output" in sys.argv:
        idx = sys.argv.index("--output")
        if idx + 1 < len(sys.argv):
            output_dir = sys.argv[idx + 1]
        else:
            print("Error: --output requires a directory path.")
            sys.exit(1)

    for dep in ("yt-dlp", "ffmpeg"):
        try:
            subprocess.run([dep, "--version"], capture_output=True, timeout=10)
        except FileNotFoundError:
            print(f"Error: {dep} not found. Install it first."); sys.exit(1)

    download(url, quality, output_dir)

if __name__ == "__main__":
    main()