{"id":25293918,"url":"https://github.com/patricktrainer/ducktube","last_synced_at":"2025-04-06T19:55:10.224Z","repository":{"id":272704190,"uuid":"915819462","full_name":"patricktrainer/ducktube","owner":"patricktrainer","description":"Watch videos with DuckDB","archived":false,"fork":false,"pushed_at":"2025-01-27T18:33:39.000Z","size":2358,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-13T01:56:10.642Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/patricktrainer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-01-12T22:01:23.000Z","updated_at":"2025-01-27T18:33:42.000Z","dependencies_parsed_at":"2025-01-16T05:57:15.435Z","dependency_job_id":null,"html_url":"https://github.com/patricktrainer/ducktube","commit_stats":null,"previous_names":["patricktrainer/ducktube"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patricktrainer%2Fducktube","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patricktrainer%2Fducktube/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patricktrainer%2Fducktube/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patricktrainer%2Fducktube/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/patricktrainer","download_url":"https://codeload.github.com/patricktrainer/ducktube/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247543602,"owners_count":20955865,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-02-13T01:56:18.102Z","updated_at":"2025-04-06T19:55:10.199Z","avatar_url":"https://github.com/patricktrainer.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🦆 DuckTube: Where Videos Meet SQL\n\nEver wanted to SELECT * FROM your favorite YouTube video? Now you can!\n\nDuckTube turns videos into queryable data using the power of DuckDB and WebAssembly.\n\nBecause why watch videos when you can query them? 📺\n\n![DuckTube](ducktube.gif)\n\n## 🤔 What's This All About?\n\nYou know how people say \"a picture is worth a thousand words\"? Well, I think a video is worth a million rows in a database!\n\nDuckTube breaks down videos into their pixel components and stores them in DuckDB, letting you do wild things like:\n\n```sql\n-- Find the frame with the most movement\nSELECT frame_id, \n       COUNT(*) as pixel_changes,\n       'IT MOVED!' as excitement\nFROM ducktube.main.video_frames \nWHERE value \u003e 0\nGROUP BY frame_id\nHAVING pixel_changes \u003e 1000\nORDER BY pixel_changes DESC\nLIMIT 1;\n```\n\n## 🌟 The Magic Pipeline\n\n```mermaid\nflowchart LR\n    A[YouTube] --\u003e|\"yt-dlp (yoink!)\"| B[Raw Video]\n    B --\u003e|\"Frame by Frame (like a flipbook!)\"| C[Pixel Data]\n    C --\u003e|\"PyAirbyte (wheee!)\"| D[MotherDuck]\n    D --\u003e|\"WASM (magic✨)\"| E[Your Browser]\n    \n    style A fill:#ff0000\n    style D fill:#ffb347\n    style E fill:#87CEEB\n```\n\n## 🚀 Let's Get This Party Started\n\n### 1. Clone \u0026 Install (The Basics)\n\n```bash\n# Clone like it's hot\ngit clone https://github.com/patricktrainer/ducktube.git\ncd ducktube\n\n# Install all the things!\npip install -e .  # Python stuff\nnpm install      # JavaScript jamboree\n\n# Set your super secret MotherDuck token\nexport MOTHERDUCK_TOKEN=your_secret_token_shhh\n```\n\n### 2. Process a Video (The Fun Part)\n\n```python\n# runner.py example - Turn video into data!\nfrom ducktube import VideoProcessor, MotherDuckUploader\n\n# Step 1: Grab a video\nprocessor = VideoProcessor(\n    url=\"https://www.youtube.com/watch?v=dQw4w9WgXcQ\",  # Never gonna give you up!\n    width=160,  # Smol\n    height=90,  # Very smol\n    mode=\"grayscale\"  # Black \u0026 white, like the old days\n)\n\n# Step 2: Make it rain data!\nframes = processor.process()\n\n# Step 3: Send it to the cloud!\nuploader = MotherDuckUploader(token=\"your_secret_token\")\nuploader.upload(frames)\n```\n\n### 3. Query Your Video! (The Really Fun Part)\n\n```sql\n-- Find the dancing pixels!\nWITH dancing_pixels AS (\n    SELECT frame_id, \n           COUNT(*) as moves\n    FROM video_frames\n    WHERE value \u003e 0\n    GROUP BY frame_id\n),\nrick_stats AS (\n    SELECT \n        MIN(moves) as least_dancing,\n        MAX(moves) as most_dancing,\n        AVG(moves) as avg_dance_energy\n    FROM dancing_pixels\n)\nSELECT \n    CASE \n        WHEN most_dancing \u003e 5000 THEN 'Rick is really rolling!'\n        ELSE 'Rickroll intensity: Medium'\n    END as dance_analysis\nFROM rick_stats;\n```\n\n## 🎮 Web Player Features\n\nThe web player isn't just a player - it's a time machine for your pixels!\n\nBuilt with React and powered by DuckDB's WASM client:\n\n```javascript\n// The simplest video player you've ever seen \n// (that's secretly powered by a database)\nconst DuckPlayer = () =\u003e {\n  const [currentFrame, setCurrentFrame] = useState(0);\n  const [isRocking, setIsRocking] = useState(false);\n\n  const loadFrame = async (frameId) =\u003e {\n    const pixels = await db.query(`\n      SELECT x, y, value\n      FROM video_frames\n      WHERE frame_id = ${frameId}\n      AND video_url = 'never_gonna_give_you_up.mp4'\n    `);\n    renderPixels(pixels); // Magic happens here! ✨\n  };\n\n  return (\n    \u003cdiv className=\"duck-player\"\u003e\n      \u003cbutton onClick={() =\u003e setIsRocking(!isRocking)}\u003e\n        {isRocking ? '🦆 Pause' : '🦆 Play'}\n      \u003c/button\u003e\n      {/* More awesome UI stuff */}\n    \u003c/div\u003e\n  );\n};\n```\n\n## 🧙‍♂️ The Secret Sauce\n\nThe data model is simple yet powerful, like a duck wearing a tuxedo:\n\n```mermaid\nerDiagram\n    VIDEO_FRAMES {\n        int frame_id \"Which moment in time?\"\n        int x \"Where horizontally?\"\n        int y \"Where vertically?\"\n        int value \"How bright?\"\n        int r \"Red (optional)\"\n        int g \"Green (optional)\"\n        int b \"Blue (optional)\"\n        string video_url \"Which video?\"\n        timestamp processed_at \"When did we process this?\"\n    }\n```\n\n## 🎯 Cool Things You Can Build\n\n1. **Motion Tracker 3000**\n\n   ```sql\n   -- Find the frame with the sickest dance moves\n   SELECT frame_id, COUNT(*) as dance_intensity\n   FROM ducktube.main.video_frames\n   WHERE value \u003e 0\n   GROUP BY frame_id\n   ORDER BY dance_intensity DESC\n   LIMIT 1;\n   ```\n\n2. **Color Party Analytics**\n\n   ```sql\n   -- What's the most common color in the video?\n   SELECT \n     r, g, b,\n     COUNT(*) as frequency,\n     'RGB Party!' as celebration\n   FROM ducktube.main.video_frames\n   WHERE r IS NOT NULL\n   GROUP BY r, g, b\n   ORDER BY frequency DESC;\n   ```\n\n![RGB Party](image.png)\n\n3. **Scene Change Detector**\n\n   ```sql\n   -- Find dramatic moments!\n   WITH frame_changes AS (\n     SELECT \n       frame_id,\n       LAG(value) OVER (ORDER BY frame_id) as prev_value,\n       value as current_value\n     FROM ducktube.main.video_frames\n   )\n   SELECT frame_id, \n          ABS(current_value - prev_value) as drama_factor\n   FROM frame_changes\n   WHERE drama_factor \u003e 100\n   ORDER BY drama_factor DESC;\n   ```\n\n## 🚨 Known Issues\n\n- Sometimes the ducks get tired and need a nap\n- Processing Rick Astley videos may cause spontaneous dancing\n- Excessive SQL querying may result in pixel fatigue\n\n## 🎮 Try It Yourself\n\n1. Get your MotherDuck token (promise we won't share it with other ducks)\n2. Process a video (any video, we don't judge)\n3. Query until your heart's content!\n\n```bash\n# Quick start (for the impatient)\nexport MOTHERDUCK_TOKEN=quack\npython process_video.py --url \"https://youtube.com/watch?v=your_video\" --mode grayscale\nnpm run start  # Fire up the web player!\n```\n\n## 🤝 Contributing\n\nGot ideas? We've got queries! Feel free to:\n\n- Add more video processing modes (Pixel art anyone?)\n- Implement frame caching (because speed!)\n- Create cool visualizations (make those pixels dance!)\n- Write more awesome queries (SQL is art!)\n\n## 📝 License\n\nMIT License - Do whatever you want with this code! Just don't forget to give the ducks credit. 🦆\n\nRemember: Keep Calm and Query On! 🦆✨\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpatricktrainer%2Fducktube","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpatricktrainer%2Fducktube","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpatricktrainer%2Fducktube/lists"}