Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 58 additions & 21 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,46 +1,83 @@
# Thinkific-Downloader Environment Configuration
# Copy this file to .env and fill in your actual values

# ===============================================
# REQUIRED AUTHENTICATION
# ===============================================

# For downloading all content, use the course link.
COURSE_LINK="https://your-thinkific-site.com/api/course_player/v2/courses/your-course-name"

# For selective content downloads, use the JSON file created from Thinki Parser.
# Copy the file to the Thinkifi Downloader root folder.
# Specify the file name below. Ex. COURSE_DATA_FILE="modified-course.json"
COURSE_DATA_FILE=""

# Client date header - Get this from browser Developer Tools Network tab
CLIENT_DATE="2025-09-23T07:42:31.512Z"

# Browser session cookie - Get this from browser Developer Tools Application/Storage tab
# IMPORTANT: Keep this secret and never share it!
COOKIE_DATA="_thinkific_session=YOUR_SESSION_COOKIE_HERE"

# ===============================================
# BASIC SETTINGS
# ===============================================

# Quality Available: "Original File", "1080p", "720p", "540p", "360p", "224p"
# Recommended: "720p" for good quality and reasonable file size
VIDEO_DOWNLOAD_QUALITY="720p"

# Set to true to download all available video formats/qualities
# Warning: This will significantly increase download size and time
# ALL_VIDEO_FORMATS=false
# Set download directory (defaults to ./downloads)
# All course content will be downloaded to this directory
OUTPUT_DIR="./downloads"

# ===============================================
# ENHANCED FEATURES
# ===============================================

# Number of concurrent downloads (default: 3, recommended: 1-5)
# Higher numbers may trigger rate limiting
CONCURRENT_DOWNLOADS=3

# Delay between downloads in seconds (default: 1.0)
# Increase if you encounter rate limiting issues
DOWNLOAD_DELAY=1.0

# Number of retry attempts for failed downloads (default: 3)
RETRY_ATTEMPTS=3

# Rate limiting in MB/s (default: unlimited)
# Set a value to limit download speed (e.g., RATE_LIMIT_MB_S=5.0)
# RATE_LIMIT_MB_S=

# File validation after download (default: true)
# Validates file integrity and size
VALIDATE_DOWNLOADS=true

# Resume partial downloads (default: true)
# Automatically resume interrupted downloads
RESUME_PARTIAL=true

# Debug mode (default: false)
# Enable detailed logging for troubleshooting
DEBUG=false

# ===============================================
# ADVANCED SETTINGS
# ===============================================

# Set to true to enable ffmpeg presentation merging (requires ffmpeg installed)
# This combines multi-part presentations into single video files
# FFMPEG_PRESENTATION_MERGE=false

# Optional: Set download directory (defaults to ./downloads)
# OUTPUT_DIR="./downloads"
FFMPEG_PRESENTATION_MERGE=false

# Optional: Number of concurrent downloads (default: 2)
# Higher numbers may trigger rate limiting
# CONCURRENT_DOWNLOADS=2
# ===============================================
# OPTIONAL FEATURES (LEGACY SUPPORT)
# ===============================================

# Optional: Delay between downloads in seconds (default: 1)
# Increase if you encounter rate limiting issues
# DOWNLOAD_DELAY=1
# For selective content downloads, use the JSON file created from Thinki Parser.
# Copy the file to the Thinkifi Downloader root folder.
# Specify the file name below. Ex. COURSE_DATA_FILE="modified-course.json"
# COURSE_DATA_FILE=""

# Optional: Number of retry attempts for failed downloads (default: 3)
# RETRY_ATTEMPTS=3
# Set to true to download all available video formats/qualities
# Warning: This will significantly increase download size and time
# ALL_VIDEO_FORMATS=false

# Optional: Log level (DEBUG, INFO, WARNING, ERROR)
# Log level (DEBUG, INFO, WARNING, ERROR)
# LOG_LEVEL="INFO"
43 changes: 43 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: 🧪 CI

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
test:
name: 🧪 Basic Tests
runs-on: ubuntu-latest

steps:
- name: 🏗️ Checkout
uses: actions/checkout@v4

- name: 🐍 Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.11'

- name: 📦 Install dependencies
run: |
pip install -r requirements.txt

- name: 🧪 Test imports
run: |
python -c "import thinkific_downloader; print('✅ Package imports work')"
python -c "from thinkific_downloader.config import Settings; print('✅ Config works')"

docker:
name: 🐳 Docker Build
runs-on: ubuntu-latest

steps:
- name: 🏗️ Checkout
uses: actions/checkout@v4

- name: 🐳 Build Docker image
run: |
docker build -t thinkific-downloader:test .
echo "✅ Docker image builds successfully"
117 changes: 117 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
name: 🚀 Release

on:
push:
tags:
- 'v*.*.*'

jobs:
release:
name: 📦 Create Release
runs-on: ubuntu-latest

steps:
- name: 🏗️ Checkout
uses: actions/checkout@v4

- name: 🏷️ Get version
id: version
run: echo "tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT

- name: 🎉 Create Release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.version.outputs.tag }}
release_name: 🚀 Release ${{ steps.version.outputs.tag }}
body: |
## 🎉 New Release: ${{ steps.version.outputs.tag }}

### 🚀 Installation Options

**Docker Hub:**
```bash
docker pull kvnxo/thinkific-downloader:${{ steps.version.outputs.tag }}
# or
docker pull kvnxo/thinkific-downloader:latest
```

**GitHub Packages:**
```bash
docker pull ghcr.io/itskavin/thinkific-downloader:${{ steps.version.outputs.tag }}
# or
docker pull ghcr.io/itskavin/thinkific-downloader:latest
```

**Setup and Run:**
```bash
git clone https://github.com/itskavin/Thinkific-Downloader.git
cd Thinkific-Downloader
cp .env.example .env
# Edit .env with your details
docker-compose up
```

**Python Direct:**
```bash
git clone https://github.com/itskavin/Thinkific-Downloader.git
cd Thinkific-Downloader
pip install -r requirements.txt
python thinkificdownloader.py
```

### 🎯 Key Features
- Downloads to `./downloads/` by default
- Docker support for easy setup
- Parallel downloads
- Smart resume functionality
draft: false
prerelease: false

docker:
name: 🐳 Build Docker
runs-on: ubuntu-latest
needs: release
if: success()

steps:
- name: 🏗️ Checkout
uses: actions/checkout@v4

- name: 🏷️ Get version
id: version
run: echo "tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT

- name: 🔑 Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- name: 🔑 Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: 🔧 Build and Push to Docker Hub
run: |
docker build -t kvnxo/thinkific-downloader:latest .
docker build -t kvnxo/thinkific-downloader:${{ steps.version.outputs.tag }} .
docker push kvnxo/thinkific-downloader:latest
docker push kvnxo/thinkific-downloader:${{ steps.version.outputs.tag }}

- name: 📦 Build and Push to GitHub Packages
run: |
# Convert repository name to lowercase for GitHub Container Registry
REPO_LOWER=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]')

# Build and tag for GitHub Container Registry
docker build -t ghcr.io/${REPO_LOWER}:latest .
docker build -t ghcr.io/${REPO_LOWER}:${{ steps.version.outputs.tag }} .

# Push to GitHub Container Registry
docker push ghcr.io/${REPO_LOWER}:latest
docker push ghcr.io/${REPO_LOWER}:${{ steps.version.outputs.tag }}
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ __pycache__/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
Expand Down Expand Up @@ -93,4 +92,5 @@ ffmpeg.log
# Docker runtime artifacts (keep config files in git)
.docker/
docker-volumes/
*.pid
*.pid
thinkific-launch-accelerator-course-october-2025/.download_status.json.bak
30 changes: 22 additions & 8 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,15 @@ graph TB
- Retry logic with exponential backoff
- Resource management

#### **4. File Validator** (`file_validator.py`)
- **Purpose**: Smart file validation and skip logic

#### **4. Resume Tracker** (`resume_tracker.py`)
- **Purpose**: Atomic, cross-platform resume and status tracking
- **Features**:
- File integrity checking (size, checksums)
- Resume detection and validation
- Download metadata persistence
- Smart skip decisions
- Download status tracking and backup (Windows, Mac, Linux)
- File integrity checking (size, checksums)
- Resume detection and validation
- Download metadata persistence
- Smart skip decisions

#### **5. Content Processors**
- **Wistia Downloader** (`wistia_downloader.py`): Video processing
Expand Down Expand Up @@ -179,7 +181,7 @@ graph TD
3. **Course Processing**: API calls → Content parsing → Task creation
4. **Download Orchestration**: Task queue → `download_manager.py` → Parallel workers
5. **Progress Tracking**: Thread-safe updates → `progress_manager.py` → Rich UI
6. **Validation**: File checks → `file_validator.py` → Skip decisions
6. **Validation**: File checks → `resume_tracker.py` → Skip decisions

---

Expand Down Expand Up @@ -286,7 +288,7 @@ tests/
├── unit/
│ ├── test_progress_manager.py
│ ├── test_download_manager.py
│ ├── test_file_validator.py
│ ├── test_resume_tracker.py
│ └── test_enhanced_downloader.py
├── integration/
│ ├── test_full_download.py
Expand Down Expand Up @@ -339,6 +341,18 @@ class TestProgressManager:
# Assert
assert file_id in progress_manager.downloads
assert download.filename == filename

def test_resume_tracker_atomic_save(self):
from thinkific_downloader.resume_tracker import ResumeTracker
import tempfile
with tempfile.TemporaryDirectory() as tmpdir:
status_file = Path(tmpdir) / ".download_status.json"
tracker = ResumeTracker(str(status_file))
tracker.status_data["test"] = {"status": "completed"}
tracker._save_status()
assert status_file.exists()
backup_file = status_file.with_suffix('.json.bak')
assert backup_file.exists()
Comment on lines +345 to +355
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The example test test_resume_tracker_atomic_save is placed within the TestProgressManager class. For better test organization and clarity, tests related to ResumeTracker should be in their own test class, such as TestResumeTracker, and ideally in a separate test_resume_tracker.py file.


@patch('thinkific_downloader.progress_manager.time.time')
def test_calculate_download_speed(self, mock_time, progress_manager):
Expand Down
Loading
Loading