Skip to content

Commit

Permalink
feat: initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
davidecavestro committed Nov 17, 2024
1 parent 98e45fd commit 07874d0
Show file tree
Hide file tree
Showing 6 changed files with 289 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SRC_URL=https://...
OUT_FILE=/downloads/video.mp4
123 changes: 123 additions & 0 deletions .github/workflows/docker-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
name: Build and Push Docker Image

on:
push:
branches:
- main
tags:
- '*'
workflow_dispatch:

jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver-opts: image=moby/buildkit:latest

- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ github.repository_owner }}
password: ${{ secrets.DOCKER_PASSWORD }}

- name: Log in to the Container registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Clone repo to build
run: git clone https://github.com/${{ github.repository }}.git repo

- name: Build and push image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: |
${{ github.repository }}:${{ github.ref_name }}
ghcr.io/${{ github.repository }}:${{ github.ref_name }}
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Push latest image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: |
${{ github.repository }}:latest
ghcr.io/${{ github.repository }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max
if: startsWith(github.ref, 'refs/tags/')

- name: Docker Hub Description
uses: peter-evans/dockerhub-description@v4
with:
username: ${{ github.repository_owner }}
password: ${{ secrets.DOCKER_PASSWORD }}
repository: ${{ github.repository }}
short-description: ${{ github.event.repository.description }}
enable-url-completion: true

create-release:
runs-on: ubuntu-latest
needs: build-and-push
permissions: write-all
if: startsWith(github.ref, 'refs/tags/')

steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Update CHANGELOG
id: changelog
uses: requarks/changelog-action@v1
with:
token: ${{ github.token }}
tag: ${{ github.ref_name }}

- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref_name }}
release_name: v${{ github.ref_name }}
body: |
Container images for this release:
- Docker Hub: `${{ github.repository }}:${{ github.ref_name }}`
- GitHub Container Registry: `ghcr.io/${{ github.repository }}:${{ github.ref_name }}`
${{ steps.changelog.outputs.changes }}
draft: false
prerelease: false

- name: Commit CHANGELOG.md
uses: stefanzweifel/git-auto-commit-action@v4
with:
branch: main
commit_message: 'docs: update CHANGELOG.md for ${{ github.ref_name }} [skip ci]'
file_pattern: CHANGELOG.md

- name: Docker Hub Description
uses: peter-evans/dockerhub-description@v4
with:
username: ${{ github.repository_owner }}
password: ${{ secrets.DOCKER_PASSWORD }}
repository: ${{ github.repository }}
short-description: ${{ github.event.repository.description }}
enable-url-completion: true
11 changes: 11 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM python
RUN apt-get update && apt-get install -y \
youtube-dl \
&& ln -s /usr/bin/yt-dlp /usr/local/bin/youtube-dl \
&& rm -rf /var/lib/apt/lists/*
RUN python -m pip install requests tqdm moviepy ffmpeg-python
COPY video.py /video.py
VOLUME /downloads
WORKDIR /downloads
ENTRYPOINT ["python"]
CMD ["/video.py"]
55 changes: 55 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# vimeo-dl

A container image based on [Javi3rV script](https://gist.github.com/alexeygrigorev/a1bc540925054b71e1a7268e50ad55cd?permalink_comment_id=5279414#gistcomment-5279414) to download segmented videos from vimeo.
It supports playlist.json and master.json urls.


## Example usage

### From docker CLI

```bash
docker run \
-e 'SRC_URL=https://...' \
-e 'OUT_FILE=/downloads/video.mp4' \
-v $(pwd)/out:/downloads \
--rm -it davidecavestro/vimeo-dl
```

### From docker compose

```yaml
version: "3"

services:
downloader:
build:
context: .
volumes:
- ./out:/downloads
environment:
- SRC_URL=${SRC_URL}
- OUT_FILE=${OUT_FILE}
- MAX_WORKERS=${MAX_WORKERS}
```
passing the url from `.env` file
```.env
SRC_URL=https://...
OUT_FILE=/downloads/video.mp4
MAX_WORKERS=5
```


## Image project home

https://github.com/davidecavestro/vimeo-dl


## Disclaimer

This software is released just for educational purposes.
**Please do not use it for illegal activities.**

## Credits

Entirely based on [alexeygrigorev](https://github.com/alexeygrigorev)'s [vimeo-download.py gist](https://gist.github.com/alexeygrigorev/a1bc540925054b71e1a7268e50ad55cd) and refining comments, just with some minor tweaks.
10 changes: 10 additions & 0 deletions compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
services:
downloader:
# build:
# context: .
image: davidecavestro/vimeo-dl
volumes:
- ./out:/downloads
environment:
- SRC_URL=${SRC_URL}
- OUT_FILE=${OUT_FILE}
88 changes: 88 additions & 0 deletions video.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import os
import sys
import base64
import requests
import subprocess
from concurrent.futures import ThreadPoolExecutor

from tqdm import tqdm
from moviepy.editor import *
import ffmpeg


url = os.getenv("SRC_URL") or input('enter [master|playlist].json url: ')
name = os.getenv("OUT_FILE") or input('enter output name: ')
max_workers = min(int(os.getenv("MAX_WORKERS", 5)), 15)

if 'master.json' in url:
url = url[:url.find('?')] + '?query_string_ranges=1'
url = url.replace('master.json', 'master.mpd')
print(url)
subprocess.run(['youtube-dl', url, '-o', name])
sys.exit(0)


def download_segment(segment_url, segment_path):
resp = requests.get(segment_url, stream=True)
if resp.status_code != 200:
print('not 200!')
print(segment_url)
return
with open(segment_path, 'wb') as segment_file:
for chunk in resp:
segment_file.write(chunk)

def download(what, to, base, max_workers):
print('saving', what['mime_type'], 'to', to)
init_segment = base64.b64decode(what['init_segment'])

segment_urls = [base + segment['url'] for segment in what['segments']]
segment_paths = [f"segment_{i}.tmp" for i in range(len(segment_urls))]

with ThreadPoolExecutor(max_workers=max_workers) as executor:
list(tqdm(executor.map(download_segment, segment_urls, segment_paths), total=len(segment_urls)))

with open(to, 'wb') as file:
file.write(init_segment)
for segment_path in segment_paths:
with open(segment_path, 'rb') as segment_file:
file.write(segment_file.read())
os.remove(segment_path)

print('done')


base_url = url[:url.rfind('/', 0, -26) + 1]
content = requests.get(url).json()

vid_heights = [(i, d['height']) for (i, d) in enumerate(content['video'])]
vid_idx, _ = max(vid_heights, key=lambda _h: _h[1])

audio_quality = [(i, d['bitrate']) for (i, d) in enumerate(content['audio'])]
audio_idx, _ = max(audio_quality, key=lambda _h: _h[1])

video = content['video'][vid_idx]
audio = content['audio'][audio_idx]
base_url = base_url + content['base_url']

video_tmp_file = 'video.mp4'
audio_tmp_file = 'audio.mp4'

download(video, video_tmp_file, base_url + video['base_url'], max_workers)
download(audio, audio_tmp_file, base_url + audio['base_url'], max_workers)

def combine_video_audio(video_file, audio_file, output_file):
try:
video_stream = ffmpeg.input(video_file)
audio_stream = ffmpeg.input(audio_file)

ffmpeg.output(video_stream, audio_stream, output_file, vcodec='copy', acodec='copy').run(overwrite_output=True)

print(f"Fragments joined into {output_file}")
except ffmpeg.Error as e:
print(f"Cannot join fragments: {e.stderr.decode()}")

combine_video_audio('video.mp4', 'audio.mp4', name)

os.remove(video_tmp_file)
os.remove(audio_tmp_file)

0 comments on commit 07874d0

Please sign in to comment.