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
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64,linux/arm/v7
platforms: linux/amd64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
Expand Down
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@ data

# Environment file
.env
config.yml
config.yml

# Frontend build
frontend/build
frontend/node_modules
47 changes: 34 additions & 13 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,21 +1,42 @@
# Dockerfile
# Multi-stage Dockerfile for Letterboxarr with web interface

# Stage 1: Build React frontend
FROM node:18-alpine as frontend-build

Check warning on line 4 in Dockerfile

View workflow job for this annotation

GitHub Actions / build-and-push

The 'as' keyword should match the case of the 'from' keyword

FromAsCasing: 'as' and 'FROM' keywords' casing do not match More info: https://docs.docker.com/go/dockerfile/rule/from-as-casing/
WORKDIR /app/frontend
COPY frontend/package*.json ./
RUN npm ci
COPY frontend/ ./
RUN npm run build

# Stage 2: Python backend with frontend
FROM python:3.12-slim

WORKDIR /app

# Install dependencies
COPY requirements.txt .
# Install system dependencies
RUN apt-get update && apt-get install -y \
gcc \
build-essential \
libffi-dev \
libssl-dev \
python3-dev \
&& rm -rf /var/lib/apt/lists/*

# Copy Python requirements and install dependencies
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

# Copy the script
COPY lib_config.py .
COPY lib_letterboxd.py .
COPY lib_radarr.py .
COPY lib_sync.py .
COPY main.py .
# Copy Python application
COPY *.py ./

# Copy built frontend
COPY --from=frontend-build /app/frontend/build ./frontend/build

# Create data directory
RUN mkdir -p ./data

# Create volume for persistent data
VOLUME ["/app/data"]
# Expose ports
EXPOSE 7373 8080

# Run the script
CMD ["python", "-u", "main.py"]
# Default command runs web server
CMD ["python", "main.py"]
112 changes: 33 additions & 79 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Automatically sync your Letterboxd lists to your Radarr instance. This script periodically checks your configured Letterboxd lists and adds any new movies to Radarr.

![Letterboxarr Preview](screenshots/dashboard.png)

## Features

- 🎬 Scrapes multiple Letterboxd lists (watchlists, collections, actors, directors, etc.)
Expand All @@ -13,13 +15,15 @@ Automatically sync your Letterboxd lists to your Radarr instance. This script pe
- ⚡ Configurable sync interval and filters
- 🎭 Per-list filtering (skip documentaries, short films, etc.)
- ⚙️ YAML configuration file support
- 🌐 Web interface for configuration and monitoring

## Prerequisites

- A running Radarr instance
- Radarr API key
- Docker and Docker Compose (for containerized deployment)
- Python 3.11+ (for local deployment)
- Node.js 18+ (for frontend development)

## Setup

Expand Down Expand Up @@ -47,48 +51,15 @@ curl -H "X-Api-Key: YOUR_API_KEY" http://your-radarr-url:7878/api/v3/rootfolder

Note the `path` of your movies folder.

### 4. Create Configuration File
### 4. Create the configuration file

Copy the example configuration:
```bash
cp config.example.yml config.yml
```

Edit `config.yml` with your settings:
```yaml
sync:
schedule:
interval_minutes: 60

radarr:
url: http://radarr:7878
api_key: your-api-key-here
quality_profile: 1
root_folder: /movies
monitor_added: true
search_added: true

letterboxd:
filters:
skip_documentaries: false
skip_short_films: false
skip_unreleased: false
skip_tv_shows: true
watch:
- username/watchlist:
tags:
- watchlist
- films/popular
- actor/daniel-day-lewis
```
Copy the [example configuration file](examples/config.example.yml) to `config.yml` and customize it with your Radarr URL, API key, quality profile, root folder, and Letterboxd lists.

## Deployment Options

### Option 1: Docker Compose (Recommended)

1. Create your `config.yml` file (see setup section above)

2. Create a docker-compose.yml file:
Create a docker-compose.yml file:

```yaml
---
Expand All @@ -97,16 +68,24 @@ services:
image: fcote/letterboxarr:latest
container_name: letterboxarr
restart: unless-stopped
ports:
- "7373:7373" # Web interface
volumes:
- ./config.yml:/app/config.yml # Configuration file
- ./data:/app/data # Persistent storage for tracking processed movies
environment:
- SECRET_KEY=${SECRET_KEY:-your-secret-key-change-this-in-production}
- ADMIN_USERNAME=${ADMIN_USERNAME:-admin}
- ADMIN_PASSWORD=${ADMIN_PASSWORD:-admin}
```

3. Build and run:
Build and run:
```bash
docker-compose up -d
```

Access the web interface at `http://localhost:7373`

### Option 2: Docker Run

```bash
Expand All @@ -115,63 +94,30 @@ docker build -t letterboxarr .
docker run -d \
--name letterboxarr \
--restart unless-stopped \
-p 7373:7373 \
-v $(pwd)/config.yml:/app/config.yml \
-v $(pwd)/data:/app/data \
-e SECRET_KEY=your-secret-key-change-this \
-e ADMIN_USERNAME=admin \
-e ADMIN_PASSWORD=admin \
letterboxarr
```

### Option 3: Local Python
Then access the web interface at `http://localhost:7373`

#### With Configuration File (Recommended)
### Option 3: Local Development

1. Install dependencies:
Install Python dependencies:
```bash
cd frontend && npm install && npm run build
pip install -r requirements.txt
```

2. Create your `config.yml` file (see setup section above)

3. Run the script:
Run the web server:
```bash
python main.py
```

## Configuration Reference

### YAML Configuration (config.yml)

```yaml
sync:
schedule:
interval_minutes: 60 # How often to check for new movies

radarr:
url: http://radarr:7878 # Radarr instance URL
api_key: your-api-key # Your Radarr API key (required)
quality_profile: 1 # Quality profile ID to use
root_folder: /movies # Root folder path for movies
monitor_added: true # Monitor newly added movies
search_added: true # Start searching for newly added movies

letterboxd:
filters: # Global filters (can be overridden per-list)
skip_documentaries: false # Skip documentary films
skip_short_films: false # Skip short films
skip_unreleased: false # Skip unreleased films
skip_tv_shows: true # Skip TV shows/series

watch: # List of Letterboxd pages to monitor
- username/watchlist # Simple watchlist
- username/watchlist: # Watchlist with custom settings
filters:
skip_documentaries: true
tags:
- watchlist
- films/popular # Popular films
- actor/daniel-day-lewis # Actor filmography
- director/david-fincher # Director filmography
```

### Supported Letterboxd List Types

- **User Lists**: `username/watchlist`, `username/films`, `username/diary`
Expand All @@ -185,6 +131,14 @@ letterboxd:

Movies from each list can be automatically tagged in Radarr. Filters can be applied globally or per-list to skip certain types of content.

### Authentication

The web interface is protected by authentication. Default credentials:
- Username: `admin` (configurable via `ADMIN_USERNAME` environment variable)
- Password: `admin` (configurable via `ADMIN_PASSWORD` environment variable)

**Important**: Change these credentials in production by setting the environment variables.

## Data Persistence

The script maintains a `processed_movies.json` file to track which movies have been processed. This prevents duplicate additions and unnecessary API calls. This file is stored in the `/app/data` directory in the container (mapped to `./data` on the host).
Expand Down
31 changes: 15 additions & 16 deletions examples/config.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@

---
sync:
schedule:
interval_minutes: 60 # How often to check for new movies (in minutes)
interval_minutes: 60 # How often to check for new movies (in minutes)

radarr:
url: http://radarr:7878 # Radarr instance URL
Expand All @@ -27,26 +26,26 @@ letterboxd:
# Each item can be a simple string or a dictionary with filters/tags

# Simple watchlist
- username/watchlist
- path: username/watchlist

# Watchlist with custom filters and tags
- username/watchlist:
filters: # Override global filters for this list
skip_documentaries: true
skip_short_films: true
tags: # Tags to add to movies from this list
- watchlist
- high-priority
- path: username/watchlist
filters: # Override global filters for this list
skip_documentaries: true
skip_short_films: true
tags: # Tags to add to movies from this list
- watchlist
- high-priority

# Collections, popular lists, etc.
- films/in/the-godfather-collection
- films/popular/this/year
- films/popular/this/decade
- path: films/in/the-godfather-collection
- path: films/popular/this/year
- path: films/popular/this/decade

# Actor, director, writer pages
- actor/daniel-day-lewis
- director/david-fincher
- writer/aaron-sorkin
- path: actor/daniel-day-lewis
- path: director/david-fincher
- path: writer/aaron-sorkin

# Filter cookie format reference:
# Letterboxd uses these filter names in cookies:
Expand Down
8 changes: 7 additions & 1 deletion examples/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ services:
image: fcote/letterboxarr:latest
container_name: letterboxarr
restart: unless-stopped
ports:
- "7373:7373" # Web interface
volumes:
- ./config.yml:/app/config.yml # Configuration file
- ./config.yml:/app/config.yml # Configuration file (read-only)
- ./data:/app/data # Persistent storage for tracking processed movies
environment:
- SECRET_KEY=${SECRET_KEY:-your-secret-key-change-this-in-production}
- ADMIN_USERNAME=${ADMIN_USERNAME:-admin}
- ADMIN_PASSWORD=${ADMIN_PASSWORD:-admin}
Loading
Loading