Compare commits

..

No commits in common. "main" and "3.2.0" have entirely different histories.
main ... 3.2.0

276 changed files with 10455 additions and 26250 deletions

22
.chglog/CHANGELOG.md.tpl Normal file
View File

@ -0,0 +1,22 @@
{{ range .Versions }}<a name="{{ .Tag.Name }}"></a>
## {{ if .Tag.Previous }}[{{ .Tag.Name }}]({{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }}){{ else }}{{ .Tag.Name }}{{ end }} ({{ datetime "2006-01-02" .Tag.Date }})
- [Release note](https://libretime.org/docs/releases/{{ .Tag.Name }}/)
{{ range .CommitGroups -}}
### {{ .Title }}
{{ range reverse .Commits -}}
- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
{{ end }}
{{ end -}}
{{- if .RevertCommits -}}
### Reverts
{{ range .RevertCommits -}}
- {{ .Revert.Header }}
{{ end }}
{{ end -}}
{{ end -}}

25
.chglog/config.yml Normal file
View File

@ -0,0 +1,25 @@
style: github
template: CHANGELOG.md.tpl
info:
title: CHANGELOG
repository_url: https://github.com/libretime/libretime
options:
commits:
filters:
Type: [feat, fix, docs, test, ci]
sort_by: Date
commit_groups:
title_maps:
feat: Features
fix: Bug Fixes
docs: Documentation
test: Tests
ci: CI
sort_by: Custom
title_order: [feat, fix, docs, test, ci]
header:
pattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$"
pattern_maps:
- Type
- Scope
- Subject

View File

@ -1,20 +0,0 @@
{
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
"bootstrap-sha": "26737abad231d96fc198fbf12c043f2d867be79c",
"include-component-in-tag": false,
"include-v-in-tag": false,
"packages": {
".": {
"release-type": "simple",
"package-name": "libretime",
"extra-files": [
"analyzer/setup.py",
"api/setup.py",
"api-client/setup.py",
"playout/setup.py",
"shared/setup.py",
"worker/setup.py"
]
}
}
}

View File

@ -1 +0,0 @@
{".":"4.2.0"}

View File

@ -10,7 +10,7 @@
"automerge": true,
"automergeType": "branch"
},
"baseBranches": ["main"],
"baseBranches": ["main", "stable"],
"labels": ["dependencies"],
"packageRules": [
{

View File

@ -25,11 +25,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4
- uses: actions/setup-python@v4
with:
python-version: "3.x"
- uses: actions/cache@v4
- uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ inputs.context }}-${{ hashFiles(format('{0}/{1}', inputs.context, '**/setup.py')) }}
@ -63,9 +63,9 @@ jobs:
shell: bash
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4
- uses: actions/cache@v4
- uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ matrix.release }}-pip-${{ inputs.context }}-${{ hashFiles(format('{0}/{1}', inputs.context, '**/setup.py')) }}
@ -77,7 +77,7 @@ jobs:
working-directory: ${{ inputs.context }}
- name: Report coverage
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@v3
with:
files: ${{ inputs.context }}/coverage.xml
flags: ${{ inputs.context }}

View File

@ -3,7 +3,7 @@ name: Analyzer
on:
workflow_dispatch:
push:
branches: [main, stable-*]
branches: [main, stable]
paths:
- .github/workflows/_python.yml
- .github/workflows/analyzer.yml
@ -12,7 +12,7 @@ on:
- tools/python*
pull_request:
branches: [main, stable-*]
branches: [main, stable]
paths:
- .github/workflows/_python.yml
- .github/workflows/analyzer.yml

View File

@ -3,7 +3,7 @@ name: API Client
on:
workflow_dispatch:
push:
branches: [main, stable-*]
branches: [main, stable]
paths:
- .github/workflows/_python.yml
- .github/workflows/api-client.yml
@ -12,7 +12,7 @@ on:
- tools/python*
pull_request:
branches: [main, stable-*]
branches: [main, stable]
paths:
- .github/workflows/_python.yml
- .github/workflows/api-client.yml

View File

@ -21,15 +21,15 @@ jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
- uses: actions/setup-python@v4
with:
python-version: "3.x"
- uses: actions/cache@v4
- uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-api-${{ hashFiles('api/**/setup.py') }}
@ -42,7 +42,7 @@ jobs:
- name: Get pull request commit range
if: github.event_name == 'pull_request'
run: echo "COMMIT_RANGE=${{ github.sha }}~1...${{ github.sha }}" >> $GITHUB_ENV
run: echo "COMMIT_RANGE=origin/${{ github.base_ref }}..${{ github.sha }}" >> $GITHUB_ENV
- name: Get push commit range
if: github.event_name == 'push'
@ -71,11 +71,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4
with:
fetch-depth: 0
- uses: actions/checkout@v4
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4
with:
repository: libretime/client
path: client

View File

@ -3,7 +3,7 @@ name: API
on:
workflow_dispatch:
push:
branches: [main, stable-*]
branches: [main, stable]
paths:
- .github/workflows/_python.yml
- .github/workflows/api.yml
@ -12,7 +12,7 @@ on:
- tools/python*
pull_request:
branches: [main, stable-*]
branches: [main, stable]
paths:
- .github/workflows/_python.yml
- .github/workflows/api.yml
@ -62,9 +62,9 @@ jobs:
LIBRETIME_DATABASE_HOST: postgres
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4
- uses: actions/cache@v4
- uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ matrix.release }}-pip-api-${{ hashFiles('api/**/setup.py') }}
@ -76,7 +76,7 @@ jobs:
working-directory: api
- name: Report coverage
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@v3
with:
files: api/coverage.xml
flags: api

View File

@ -1,34 +0,0 @@
name: Backport
on:
pull_request_target:
types:
- closed
- labeled
jobs:
backport:
runs-on: ubuntu-latest
# Only react to merged PRs for security reasons.
# See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target.
if: >
github.event.pull_request.merged
&& (
github.event.action == 'closed'
|| (
github.event.action == 'labeled'
&& contains(github.event.label.name, 'backport')
)
)
steps:
- uses: jooola/backport@main
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
title_template: <%= title %> (<%= base %>)
body_template: |
Backport <%= mergeCommitSha %> from #<%= number %>.
BEGIN_COMMIT_OVERRIDE
<%= title %>
END_COMMIT_OVERRIDE

View File

@ -3,9 +3,9 @@ name: Container
on:
push:
tags: ["[0-9]+.[0-9]+.[0-9]+*"]
branches: [main, stable-*]
branches: [main, stable]
pull_request:
branches: [main, stable-*]
branches: [main, stable]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@ -20,11 +20,11 @@ jobs:
if: ${{ github.repository_owner == 'libretime' }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4
- name: Update Docker Hub description
if: github.event_name == 'push'
uses: peter-evans/dockerhub-description@v4
uses: peter-evans/dockerhub-description@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
@ -44,7 +44,7 @@ jobs:
type=semver,pattern={{major}}.{{minor}}
- name: Upload metadata bake file
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: meta-${{ matrix.target }}
path: ${{ steps.meta.outputs.bake-file }}
@ -55,7 +55,7 @@ jobs:
if: ${{ github.repository_owner == 'libretime' }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4
- uses: docker/setup-buildx-action@v3
@ -76,9 +76,7 @@ jobs:
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Download all metadata bake files
uses: actions/download-artifact@v4
with:
pattern: meta-*
uses: actions/download-artifact@v3
- name: Guess LIBRETIME_VERSION
run: |
@ -86,7 +84,7 @@ jobs:
echo "LIBRETIME_VERSION=$(cat VERSION | tr -d [:blank:])" >> $GITHUB_ENV
- name: Build
uses: docker/bake-action@v5
uses: docker/bake-action@v4
with:
pull: true
push: ${{ github.event_name == 'push' }}

View File

@ -30,7 +30,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4
- name: Login to the Container registry
uses: docker/login-action@v3
@ -78,7 +78,7 @@ jobs:
EOF
- name: Build and push
uses: docker/build-push-action@v6
uses: docker/build-push-action@v5
with:
context: .
push: ${{ github.repository_owner == 'libretime' }}

View File

@ -2,14 +2,14 @@ name: Docs
on:
push:
branches: [main, stable-*]
branches: [main, stable]
paths:
- .github/vale/**
- .github/workflows/docs.yml
- docs/**
pull_request:
branches: [main, stable-*]
branches: [main, stable]
paths:
- .github/vale/**
- .github/workflows/docs.yml
@ -24,9 +24,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4
- uses: actions/cache@v4
- uses: actions/cache@v3
with:
path: |
/usr/local/bin/vale*
@ -64,11 +64,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4
with:
fetch-depth: 0
- uses: actions/checkout@v4
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4
with:
repository: libretime/website
path: website

View File

@ -20,9 +20,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4
- uses: actions/setup-node@v4
- uses: actions/setup-node@v3
with:
node-version: "16"
@ -38,9 +38,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4
- uses: actions/cache@v4
- uses: actions/cache@v3
with:
path: .lycheecache
key: housekeeping-find-broken-links-${{ github.sha }}
@ -48,12 +48,13 @@ jobs:
- name: Check Links
id: lychee
uses: lycheeverse/lychee-action@v2.1.0
uses: lycheeverse/lychee-action@v1.8.0
with:
args: >-
'**/*.md'
--require-https
--exclude-all-private
--exclude-mail
--exclude 'example\.(com|org)'
--exclude '\$server_name\$request_uri'
--exclude '%7Bvars.version%7D'
@ -62,8 +63,6 @@ jobs:
--exclude 'https://www.ascap.com'
--exclude 'https://www.youtube-nocookie.com'
--exclude 'github\.com/libretime/libretime/(issues|pulls)'
--exclude 'https://packages.ubuntu.com/bionic/php7.2'
--exclude 'https://packages.ubuntu.com/bionic/python3'
--cache
--max-cache-age 2d
fail: true
@ -76,7 +75,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
- uses: actions/stale@v8
with:
stale-issue-message: >
This issue has been automatically marked as stale because it has not had

View File

@ -3,14 +3,14 @@ name: Legacy
on:
workflow_dispatch:
push:
branches: [main, stable-*]
branches: [main, stable]
paths:
- .github/workflows/legacy.yml
- api/**
- legacy/**
pull_request:
branches: [main, stable-*]
branches: [main, stable]
paths:
- .github/workflows/legacy.yml
- api/**
@ -33,7 +33,7 @@ jobs:
- php-version: "7.4" # Focal, Bullseye
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
@ -53,7 +53,7 @@ jobs:
env:
ENVIRONMENT: testing
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4
- name: Setup PostgreSQL
run: |
@ -62,7 +62,6 @@ jobs:
sudo -u postgres psql -c 'CREATE DATABASE libretime;'
sudo -u postgres psql -c "CREATE USER libretime WITH PASSWORD 'libretime';"
sudo -u postgres psql -c 'GRANT CONNECT ON DATABASE libretime TO libretime;'
sudo -u postgres psql -c 'ALTER DATABASE libretime OWNER TO libretime;'
sudo -u postgres psql -c 'ALTER USER libretime CREATEDB;'
- name: Setup PHP
@ -75,7 +74,7 @@ jobs:
run: |
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- uses: actions/cache@v4
- uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
@ -95,7 +94,7 @@ jobs:
github.event_name == 'workflow_dispatch'
)
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4
with:
token: ${{ secrets.LIBRETIME_BOT_TOKEN }}

View File

@ -3,7 +3,7 @@ name: Playout
on:
workflow_dispatch:
push:
branches: [main, stable-*]
branches: [main, stable]
paths:
- .github/workflows/_python.yml
- .github/workflows/playout.yml
@ -13,7 +13,7 @@ on:
- tools/python*
pull_request:
branches: [main, stable-*]
branches: [main, stable]
paths:
- .github/workflows/_python.yml
- .github/workflows/playout.yml

View File

@ -12,7 +12,7 @@ jobs:
name: Validate PR title
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v5.5.3
- uses: amannn/action-semantic-pull-request@v5.3.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
@ -27,8 +27,6 @@ jobs:
ui
worker
deps
main
stable
subjectPattern: ^(?![A-Z]).+$
subjectPatternError: |
The subject "{subject}" found in the pull request title "{title}"

View File

@ -3,10 +3,10 @@ name: Project
on:
workflow_dispatch:
push:
branches: [main, stable-*]
branches: [main, stable]
pull_request:
types: [opened, reopened, synchronize, edited]
branches: [main, stable-*]
branches: [main, stable]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@ -15,26 +15,26 @@ jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4
- uses: actions/setup-python@v5
- uses: actions/setup-python@v4
with:
python-version: "3.x"
- uses: actions/cache@v4
- uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-project-pre-commit-pip-${{ hashFiles('.pre-commit-config.yaml') }}
restore-keys: |
${{ runner.os }}-project-pre-commit-pip
- uses: pre-commit/action@v3.0.1
- uses: pre-commit/action@v3.0.0
test-tools:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4
- uses: actions/setup-python@v4
with:
python-version: "3.x"
- run: make all

View File

@ -1,21 +0,0 @@
name: Release-Please
on:
push:
branches:
- main
- stable
jobs:
release-please:
# Do not run on forks.
if: github.repository == 'libretime/libretime'
runs-on: ubuntu-latest
steps:
- uses: google-github-actions/release-please-action@v4
with:
token: ${{ secrets.LIBRETIME_BOT_TOKEN }}
config-file: .github/release-please-config.json
manifest-file: .github/release-please-manifest.json
target-branch: ${{ github.ref_name }}

View File

@ -9,7 +9,7 @@ jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4
- uses: shivammathur/setup-php@v2
with:
php-version: 7.4
@ -22,9 +22,12 @@ jobs:
- name: Build tarball
run: make tarball
- name: Upload tarball
uses: softprops/action-gh-release@v2
- name: Create Release
uses: softprops/action-gh-release@v1
with:
body_path: docs/releases/${{ github.ref_name }}.md
draft: true
prerelease: true
files: |
libretime-*.tar.gz
sha256sums.txt

View File

@ -3,7 +3,7 @@ name: Shared
on:
workflow_dispatch:
push:
branches: [main, stable-*]
branches: [main, stable]
paths:
- .github/workflows/_python.yml
- .github/workflows/shared.yml
@ -11,7 +11,7 @@ on:
- tools/python*
pull_request:
branches: [main, stable-*]
branches: [main, stable]
paths:
- .github/workflows/_python.yml
- .github/workflows/shared.yml

View File

@ -3,7 +3,7 @@ name: Worker
on:
workflow_dispatch:
push:
branches: [main, stable-*]
branches: [main, stable]
paths:
- .github/workflows/_python.yml
- .github/workflows/worker.yml
@ -11,7 +11,7 @@ on:
- tools/python*
pull_request:
branches: [main, stable-*]
branches: [main, stable]
paths:
- .github/workflows/_python.yml
- .github/workflows/worker.yml

View File

@ -3,7 +3,7 @@
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
rev: v4.5.0
hooks:
- id: check-added-large-files
- id: check-case-conflict
@ -29,37 +29,37 @@ repos:
exclude: ^api
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.1.0
rev: v3.0.3
hooks:
- id: prettier
files: \.(md|mdx|yml|yaml|js|jsx|ts|tsx|json|css)$
exclude: ^(legacy/public(?!/js/airtime)|CHANGELOG.md$|.github/release-please-manifest.json)
exclude: ^legacy/public(?!/js/airtime)
- repo: https://github.com/asottile/pyupgrade
rev: v3.19.0
rev: v3.15.0
hooks:
- id: pyupgrade
args: [--py38-plus]
- repo: https://github.com/adamchainz/django-upgrade
rev: 1.22.2
rev: 1.15.0
hooks:
- id: django-upgrade
args: [--target-version, "4.2"]
- repo: https://github.com/pycqa/isort
rev: 5.13.2
rev: 5.12.0
hooks:
- id: isort
args: [--resolve-all-configs]
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 24.10.0
rev: 23.9.1
hooks:
- id: black
- repo: https://github.com/codespell-project/codespell
rev: v2.3.0
rev: v2.2.6
hooks:
- id: codespell
args: [--ignore-words=.codespellignore]
@ -110,11 +110,3 @@ repos:
pass_filenames: false
language: script
files: ^legacy
- id: api-schema-update
name: api-schema-update
description: Ensure API schema is up to date
entry: make -C api schema
pass_filenames: false
language: system
files: ^api

View File

@ -1,140 +1,4 @@
# Changelog
## [4.2.0](https://github.com/libretime/libretime/compare/4.1.0...4.2.0) (2024-06-22)
### Features
* **legacy:** add current date macro to string block criteria ([#3013](https://github.com/libretime/libretime/issues/3013)) ([451652b](https://github.com/libretime/libretime/commit/451652bc4002b142ab9cf33ae517451c4966134f))
* **legacy:** add filename block criteria ([#3015](https://github.com/libretime/libretime/issues/3015)) ([4642b6c](https://github.com/libretime/libretime/commit/4642b6c08ef813ab5dc7354f73141239f5c145e0))
### Bug Fixes
* pin pip version to &lt;24.1 to allow installing pytz (celery) ([#3043](https://github.com/libretime/libretime/issues/3043)) ([646bc81](https://github.com/libretime/libretime/commit/646bc817246a1e3e0d8107c2b69d726681c643b6))
* playlist allocates inaccurate time to smartblocks ([#3026](https://github.com/libretime/libretime/issues/3026)) ([2b43e51](https://github.com/libretime/libretime/commit/2b43e51ed140bf307e491f0fcb7b84f95709d604))
### Performance Improvements
* optimize the api image health check ([#3038](https://github.com/libretime/libretime/issues/3038)) ([d99d6e1](https://github.com/libretime/libretime/commit/d99d6e1a68f20b3f4255296cd22ac80a90adc020))
* optimize the rabbitmq health check ([#3037](https://github.com/libretime/libretime/issues/3037)) ([9684214](https://github.com/libretime/libretime/commit/96842144257855df86085b052ed8ff87562bc049))
## [4.1.0](https://github.com/libretime/libretime/compare/4.0.0...4.1.0) (2024-05-05)
### Features
* **api:** implement file deletion ([#2960](https://github.com/libretime/libretime/issues/2960)) ([9757b1b](https://github.com/libretime/libretime/commit/9757b1b78c98a33f233163c77eb1b2ad6e0f0efe))
* build schedule events exclusively in playout ([#2946](https://github.com/libretime/libretime/issues/2946)) ([40b4fc7](https://github.com/libretime/libretime/commit/40b4fc7f66004ee3bcb61c9961ec2c48bbcbc6cb))
* **legacy:** add aac/opus support to dashboard player ([#2881](https://github.com/libretime/libretime/issues/2881)) ([95283ef](https://github.com/libretime/libretime/commit/95283efc1f9a63376a99184ef69b699beba45802))
* **legacy:** disable public radio page and redirect to login ([#2903](https://github.com/libretime/libretime/issues/2903)) ([170d095](https://github.com/libretime/libretime/commit/170d09545e4fcfeeb95f9fc5c355329764501854))
* **legacy:** trim overbooked shows after autoloading a playlist ([#2897](https://github.com/libretime/libretime/issues/2897)) ([a95ce3d](https://github.com/libretime/libretime/commit/a95ce3d2296bb864b379dcce14090bd821c1dfc9))
* **legacy:** visual cue point editor ([#2947](https://github.com/libretime/libretime/issues/2947)) ([da02e74](https://github.com/libretime/libretime/commit/da02e74f2115cb76a6435fab5ab2667a8c622b98))
* start celery worker programmatically ([#2988](https://github.com/libretime/libretime/issues/2988)) ([9c548b3](https://github.com/libretime/libretime/commit/9c548b365ec114c6789d2a69e66cc721da6ae100))
### Bug Fixes
* **analyzer:** backslash non utf-8 data when probing replaygain ([#2931](https://github.com/libretime/libretime/issues/2931)) ([29f73e0](https://github.com/libretime/libretime/commit/29f73e0dcb1fd668a79a2ffedc33e16172277376)), closes [#2910](https://github.com/libretime/libretime/issues/2910)
* apply replay gain preferences on scheduled files ([#2945](https://github.com/libretime/libretime/issues/2945)) ([35d0dec](https://github.com/libretime/libretime/commit/35d0dec4a887cdaea2d73dc9bee60eb6624a2aca))
* **deps:** update dependency friendsofphp/php-cs-fixer to &lt;3.49.1 ([#2899](https://github.com/libretime/libretime/issues/2899)) ([3e05748](https://github.com/libretime/libretime/commit/3e05748d2d1180b8dad55b6f997e6aa7117735f1))
* **deps:** update dependency friendsofphp/php-cs-fixer to &lt;3.51.1 ([#2963](https://github.com/libretime/libretime/issues/2963)) ([22c303c](https://github.com/libretime/libretime/commit/22c303cfffdc777177bd74273e2c24da58cf1682))
* **deps:** update dependency friendsofphp/php-cs-fixer to &lt;3.53.1 ([#2972](https://github.com/libretime/libretime/issues/2972)) ([9192aaa](https://github.com/libretime/libretime/commit/9192aaa2bb2dada470e03537493160d9b14a42f4))
* **deps:** update dependency gunicorn to v22 (security) ([#2993](https://github.com/libretime/libretime/issues/2993)) ([a2cf769](https://github.com/libretime/libretime/commit/a2cf7697a97bbc4faf89fd7bc9ba9ecc235bf873))
* incorrect docker compose version ([#2975](https://github.com/libretime/libretime/issues/2975)) ([634e6e2](https://github.com/libretime/libretime/commit/634e6e236d908994d586c946bbe28bcba8a357fa))
* **installer:** setup the worker entrypoint ([#2996](https://github.com/libretime/libretime/issues/2996)) ([71b20ae](https://github.com/libretime/libretime/commit/71b20ae3c974680d814062c5a0bfa51a105dde61))
* **legacy:** allow deleting file with api token ([#2995](https://github.com/libretime/libretime/issues/2995)) ([86da46e](https://github.com/libretime/libretime/commit/86da46ee3a54676298e30301846be890d1ea93ae))
* **legacy:** allow updating track types code ([#2955](https://github.com/libretime/libretime/issues/2955)) ([270aa08](https://github.com/libretime/libretime/commit/270aa08ae6c7207de1cc3ea552dabeb018bcfe0d))
* **legacy:** avoid crash when lot of streams in configuration ([#2915](https://github.com/libretime/libretime/issues/2915)) ([12dd477](https://github.com/libretime/libretime/commit/12dd47731290bf539be7a2a81571f8ada223e9c4))
* **legacy:** ensure validation is performed on the track type form ([#2985](https://github.com/libretime/libretime/issues/2985)) ([5ad69bf](https://github.com/libretime/libretime/commit/5ad69bf0b76ff2e5065551b6a7d154cb26834605))
* **legacy:** fix hidden fields in edit file form ([#2932](https://github.com/libretime/libretime/issues/2932)) ([f4b260f](https://github.com/libretime/libretime/commit/f4b260fdf70c0dd1830166d3856239dae5366599))
* **legacy:** replay_gain_modifier should be a system preference ([#2943](https://github.com/libretime/libretime/issues/2943)) ([37d1a76](https://github.com/libretime/libretime/commit/37d1a7685e37e45734553a0eb4a4da793ca858cb))
* remove obsolete docker compose version ([#2982](https://github.com/libretime/libretime/issues/2982)) ([fb0584b](https://github.com/libretime/libretime/commit/fb0584b021fd1c966181c7ab3989938cdfe4e642))
* trigger legacy tasks manager every 5m ([#2987](https://github.com/libretime/libretime/issues/2987)) ([7040d0e](https://github.com/libretime/libretime/commit/7040d0e4bd92911a9072226f49ad59ce575d6ed9))
* **worker:** ensure celery beat is started ([#3007](https://github.com/libretime/libretime/issues/3007)) ([bfde17e](https://github.com/libretime/libretime/commit/bfde17edf7fcc2bfd55263756e6ec3e455f11740))
## [4.0.0](https://github.com/libretime/libretime/compare/3.2.0...4.0.0) (2024-01-07)
### ⚠ BREAKING CHANGES
* The media file serving is now handled by Nginx instead of the API service. The `storage.path` field is now used in the Nginx configuration, so make sure to update the Nginx configuration file if you change it.
* **installer:** The default listen port for the installer is now `8080`. We recommend that you put a reverse proxy in front of LibreTime.
* **installer:** The `--update-nginx` flag was removed from the installer. The nginx configuration deployed by the installer will now always be overwritten. Make sure to move your customizations to a reverse proxy configuration.
* The default system output (`stream.outputs.system[].kind`) changed from `alsa` to `pulseaudio`. Make sure to update your configuration file if you rely on the default system output.
* The `general.secret_key` configuration field is now required. Make sure to update your configuration file and add a secret key.
### Features
* default system output is now `pulseaudio` ([#2842](https://github.com/libretime/libretime/issues/2842)) ([083ee3f](https://github.com/libretime/libretime/commit/083ee3f1dd74441e288b4d63178ae9cea12ba286)), closes [#2542](https://github.com/libretime/libretime/issues/2542)
* disable uvicorn worker lifespan ([#2845](https://github.com/libretime/libretime/issues/2845)) ([8743c84](https://github.com/libretime/libretime/commit/8743c84d0f007a5430e9059c197a261e613cc642))
* **installer:** add the `--storage-path` flag ([#2865](https://github.com/libretime/libretime/issues/2865)) ([5b23852](https://github.com/libretime/libretime/commit/5b23852f8d144f0c7cdeb62831f7b1a27872b40e))
* **installer:** change default listen port to 8080 ([#2852](https://github.com/libretime/libretime/issues/2852)) ([f72b7f9](https://github.com/libretime/libretime/commit/f72b7f9c9727800a9d77d64c540c12f272bb0ae3))
* **installer:** remove the `--update-nginx` flag ([#2851](https://github.com/libretime/libretime/issues/2851)) ([35d7eac](https://github.com/libretime/libretime/commit/35d7eace13c2b9667fdb41fec0788118e0c5e63f))
* **playout:** configure device for alsa and pulseaudio system outputs ([#2654](https://github.com/libretime/libretime/issues/2654)) ([06af18b](https://github.com/libretime/libretime/commit/06af18b84e7dfaad95e3b55dda22ec1ddad27050))
* rewrite cloud-init config ([#2853](https://github.com/libretime/libretime/issues/2853)) ([8406d52](https://github.com/libretime/libretime/commit/8406d520d7a7bea4060be8a00e360bcf413cb2d5))
* run python in optimized mode ([#2874](https://github.com/libretime/libretime/issues/2874)) ([3f7fc99](https://github.com/libretime/libretime/commit/3f7fc99b6b343fbc8df319d8130ba8247aea96d8))
* the `general.secret_key` configuration field is now required ([#2841](https://github.com/libretime/libretime/issues/2841)) ([0d2d1a2](https://github.com/libretime/libretime/commit/0d2d1a26731a2b41ce5e574ed6de9950eaae4153)), closes [#2426](https://github.com/libretime/libretime/issues/2426)
* use nginx to serve media files ([#2860](https://github.com/libretime/libretime/issues/2860)) ([4603c17](https://github.com/libretime/libretime/commit/4603c1759f29b8a1adb3e83d610ca00e778d76bd))
### Bug Fixes
* add parent function name in setValue exception ([#2777](https://github.com/libretime/libretime/issues/2777)) ([c764a5a](https://github.com/libretime/libretime/commit/c764a5a648ac6cf6c1f63cd9be6de9fe07c40988))
* **api:** ensure non ascii paths are handled by X-Accel-Redirect ([#2861](https://github.com/libretime/libretime/issues/2861)) ([0ce63f3](https://github.com/libretime/libretime/commit/0ce63f3bf0448580170024cdde96ee351ee5c358))
* **api:** enum schema description ([#2803](https://github.com/libretime/libretime/issues/2803)) ([976b70e](https://github.com/libretime/libretime/commit/976b70ed32a0e774cc0b72b8332372be32799ed1))
* **api:** let nginx handle the media file content type ([#2862](https://github.com/libretime/libretime/issues/2862)) ([72268ad](https://github.com/libretime/libretime/commit/72268ad9bb1a96b24efda7143b9371d6fd98ca03))
* **api:** move gunicorn worker config to python file ([#2854](https://github.com/libretime/libretime/issues/2854)) ([43221d9](https://github.com/libretime/libretime/commit/43221d9d7f34ba98a14db9906e350cb494a86b25))
* **api:** paths with question marks chars are handled by X-Accel-Redirect ([#2875](https://github.com/libretime/libretime/issues/2875)) ([b2c1ceb](https://github.com/libretime/libretime/commit/b2c1ceb89fafc76f18ec650d19ec0ff03e4a20b0))
* **deps:** update dependency friendsofphp/php-cs-fixer to &lt;3.42.1 (main) ([#2765](https://github.com/libretime/libretime/issues/2765)) ([8ae4dce](https://github.com/libretime/libretime/commit/8ae4dce9e7c013c1f66f1b4d5da4a8c91d3419b7))
* **deps:** update dependency friendsofphp/php-cs-fixer to &lt;3.43.2 (main) ([#2848](https://github.com/libretime/libretime/issues/2848)) ([62e5f4d](https://github.com/libretime/libretime/commit/62e5f4dfbb76ab1919c4905570cc34274c685cef))
* **deps:** update dependency friendsofphp/php-cs-fixer to &lt;3.45.1 (main) ([#2855](https://github.com/libretime/libretime/issues/2855)) ([6f84328](https://github.com/libretime/libretime/commit/6f8432838058be6ef1cfa7858f17b8272929896e))
* **deps:** update dependency friendsofphp/php-cs-fixer to &lt;3.46.1 (main) ([#2868](https://github.com/libretime/libretime/issues/2868)) ([4827dbc](https://github.com/libretime/libretime/commit/4827dbce711262e90238bb3b6c0a35b1ce3d6877))
* **legacy:** allow uploading opus files ([#2804](https://github.com/libretime/libretime/issues/2804)) ([f252a16](https://github.com/libretime/libretime/commit/f252a16637e113ceb1dd340fb7aad31af9c23ff0))
* **legacy:** declare previously undeclared variable ([#2793](https://github.com/libretime/libretime/issues/2793)) ([e2cfbf4](https://github.com/libretime/libretime/commit/e2cfbf4c038f28874a206df5805f04f69a40647b))
* **legacy:** ensure last played criteria works with never played files ([#2840](https://github.com/libretime/libretime/issues/2840)) ([24ee383](https://github.com/libretime/libretime/commit/24ee3830c23f7147f82febe3d3c6743d5ae8d4e6))
* **playout:** increase file download chunk size to 8192 bytes ([#2863](https://github.com/libretime/libretime/issues/2863)) ([7ed1be1](https://github.com/libretime/libretime/commit/7ed1be1816abef20b9ae59a8c66a9e48a34f37c5))
* **playout:** remove empty file when the download request failed ([#2864](https://github.com/libretime/libretime/issues/2864)) ([2facbfa](https://github.com/libretime/libretime/commit/2facbfaff23d4df0e7531b82f04f932bb2c4c9a4))
* **worker:** unbound variable when episode url returns HTTP 404 ([#2844](https://github.com/libretime/libretime/issues/2844)) ([3f39689](https://github.com/libretime/libretime/commit/3f396895e588e62183e01d17927d9bdbea512ee0))
## [3.2.0](https://github.com/libretime/libretime/compare/3.1.0...3.2.0) (2023-10-16)
- [Release note](https://libretime.org/docs/releases/3.2.0/)
### Features
- **legacy:** move session store to database ([#2523](https://github.com/libretime/libretime/issues/2523))
- **api:** add email configuration
- add mobile devices stream config field ([#2744](https://github.com/libretime/libretime/issues/2744))
### Bug Fixes
- **playout:** liquidsoap aac output syntax errors
- **deps:** update dependency friendsofphp/php-cs-fixer to <3.20.1 (stable) ([#2602](https://github.com/libretime/libretime/issues/2602))
- **deps:** update dependency friendsofphp/php-cs-fixer to <3.21.2 ([#2612](https://github.com/libretime/libretime/issues/2612))
- libretime process leaks and lsof high cpu usage ([#2615](https://github.com/libretime/libretime/issues/2615))
- **deps:** update dependency friendsofphp/php-cs-fixer to <3.22.1 ([#2633](https://github.com/libretime/libretime/issues/2633))
- libretime process leaks and lsof high cpu usage ([#2615](https://github.com/libretime/libretime/issues/2615))
- **deps:** update dependency friendsofphp/php-cs-fixer to <3.23.1 (stable) ([#2656](https://github.com/libretime/libretime/issues/2656))
- **deps:** update dependency friendsofphp/php-cs-fixer to <3.26.1 (stable) ([#2678](https://github.com/libretime/libretime/issues/2678))
- **deps:** update dependency friendsofphp/php-cs-fixer to <3.26.1 (main) ([#2677](https://github.com/libretime/libretime/issues/2677))
- **deps:** update dependency friendsofphp/php-cs-fixer to <3.26.2 ([#2686](https://github.com/libretime/libretime/issues/2686))
- **deps:** update dependency friendsofphp/php-cs-fixer to <3.26.2 ([#2687](https://github.com/libretime/libretime/issues/2687))
- **deps:** update dependency friendsofphp/php-cs-fixer to <3.27.1 (main) ([#2714](https://github.com/libretime/libretime/issues/2714))
- **deps:** update dependency friendsofphp/php-cs-fixer to <3.27.1 (stable) ([#2715](https://github.com/libretime/libretime/issues/2715))
- **deps:** update dependency friendsofphp/php-cs-fixer to <3.34.1 ([#2723](https://github.com/libretime/libretime/issues/2723))
- **deps:** update dependency friendsofphp/php-cs-fixer to <3.35.2 ([#2738](https://github.com/libretime/libretime/issues/2738))
- **deps:** update dependency friendsofphp/php-cs-fixer to <3.35.2 ([#2722](https://github.com/libretime/libretime/issues/2722))
### Documentation
- update chat links to point to matrix ([#2571](https://github.com/libretime/libretime/issues/2571))
- fix broken link ([#2616](https://github.com/libretime/libretime/issues/2616))
### Tests
- **playout:** check unsupported liquidsoap aac output
<a name="3.1.0"></a>
## [3.1.0](https://github.com/libretime/libretime/compare/3.0.2...3.1.0) (2023-05-26)
@ -326,6 +190,8 @@
- chore(api): install django-rest-framework from git ([#2518](https://github.com/libretime/libretime/issues/2518))
<a name="3.0.2"></a>
## [3.0.2](https://github.com/libretime/libretime/compare/3.0.1...3.0.2) (2023-02-21)
- [Release note](https://libretime.org/docs/releases/3.0.2/)
@ -357,6 +223,8 @@
- don't squash commits during docs sync
- test project weekly
<a name="3.0.1"></a>
## [3.0.1](https://github.com/libretime/libretime/compare/3.0.0...3.0.1) (2022-12-20)
- [Release note](https://libretime.org/docs/releases/3.0.1/)
@ -385,6 +253,8 @@
- sync docs with libretime/website repository
- pin vale version to v2.21.3
<a name="3.0.0"></a>
## [3.0.0](https://github.com/libretime/libretime/compare/3.0.0-beta.2...3.0.0) (2022-10-10)
- [Release note](https://libretime.org/docs/releases/3.0.0/)
@ -405,6 +275,8 @@
- **analyzer:** fix wrong bit_rate values
<a name="3.0.0-beta.2"></a>
## [3.0.0-beta.2](https://github.com/libretime/libretime/compare/3.0.0-beta.1...3.0.0-beta.2) (2022-10-03)
- [Release note](https://libretime.org/docs/releases/3.0.0-beta.2/)
@ -433,6 +305,8 @@
- allow failure when linting /docs/releases
- use github.ref_name to get tag
<a name="3.0.0-beta.1"></a>
## [3.0.0-beta.1](https://github.com/libretime/libretime/compare/3.0.0-beta.0...3.0.0-beta.1) (2022-09-23)
- [Release note](https://libretime.org/docs/releases/3.0.0-beta.1/)
@ -465,6 +339,8 @@
- don't check github.com/libretime/libretime/(issues|pulls) links
- run docs workflow on vale files changes
<a name="3.0.0-beta.0"></a>
## [3.0.0-beta.0](https://github.com/libretime/libretime/compare/3.0.0-alpha.13...3.0.0-beta.0) (2022-09-16)
- [Release note](https://libretime.org/docs/releases/3.0.0-beta.0/)
@ -614,6 +490,8 @@
- improve containers build caching
- add container tags
<a name="3.0.0-alpha.13"></a>
## [3.0.0-alpha.13](https://github.com/libretime/libretime/compare/3.0.0-alpha.12...3.0.0-alpha.13) (2022-07-15)
- [Release note](https://libretime.org/docs/releases/3.0.0-alpha.13/)
@ -774,6 +652,8 @@
- disable codecov project status check
- disable codecov patch status check
<a name="3.0.0-alpha.12"></a>
## [3.0.0-alpha.12](https://github.com/libretime/libretime/compare/3.0.0-alpha.11...3.0.0-alpha.12) (2022-03-29)
- [Release note](https://libretime.org/docs/releases/3.0.0-alpha.12/)
@ -788,6 +668,8 @@
- add missing data to release note
- fix and update links ([#1714](https://github.com/libretime/libretime/issues/1714))
<a name="3.0.0-alpha.11"></a>
## [3.0.0-alpha.11](https://github.com/libretime/libretime/compare/3.0.0-alpha.10...3.0.0-alpha.11) (2022-03-28)
- [Release note](https://libretime.org/docs/releases/3.0.0-alpha.11/)

View File

@ -2,7 +2,7 @@ ARG LIBRETIME_VERSION
#======================================================================================#
# Python Builder #
#======================================================================================#
FROM python:3.10-slim-bullseye AS python-builder
FROM python:3.10-slim-bullseye as python-builder
WORKDIR /build
@ -18,7 +18,7 @@ RUN pip wheel --wheel-dir . --no-deps .
#======================================================================================#
# Python base #
#======================================================================================#
FROM python:3.10-slim-bullseye AS python-base
FROM python:3.10-slim-bullseye as python-base
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
@ -48,7 +48,7 @@ RUN set -eux \
#======================================================================================#
# Python base with ffmpeg #
#======================================================================================#
FROM python-base AS python-base-ffmpeg
FROM python-base as python-base-ffmpeg
RUN set -eux \
&& DEBIAN_FRONTEND=noninteractive apt-get update \
@ -59,7 +59,7 @@ RUN set -eux \
#======================================================================================#
# Analyzer #
#======================================================================================#
FROM python-base-ffmpeg AS libretime-analyzer
FROM python-base-ffmpeg as libretime-analyzer
COPY tools/packages.py /tmp/packages.py
COPY analyzer/packages.ini /tmp/packages.ini
@ -97,7 +97,7 @@ ENV LIBRETIME_VERSION=$LIBRETIME_VERSION
#======================================================================================#
# Playout #
#======================================================================================#
FROM python-base-ffmpeg AS libretime-playout
FROM python-base-ffmpeg as libretime-playout
COPY tools/packages.py /tmp/packages.py
COPY playout/packages.ini /tmp/packages.ini
@ -136,12 +136,11 @@ ENV LIBRETIME_VERSION=$LIBRETIME_VERSION
#======================================================================================#
# API #
#======================================================================================#
FROM python-base AS libretime-api
FROM python-base as libretime-api
RUN set -eux \
&& DEBIAN_FRONTEND=noninteractive apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
curl \
gcc \
libc6-dev \
libpq-dev \
@ -167,7 +166,7 @@ WORKDIR /app
CMD ["/usr/local/bin/gunicorn", \
"--workers=4", \
"--worker-class=libretime_api.gunicorn.Worker", \
"--worker-class=uvicorn.workers.UvicornWorker", \
"--log-file", "-", \
"--bind=0.0.0.0:9001", \
"libretime_api.asgi"]
@ -175,12 +174,13 @@ CMD ["/usr/local/bin/gunicorn", \
ARG LIBRETIME_VERSION
ENV LIBRETIME_VERSION=$LIBRETIME_VERSION
HEALTHCHECK CMD ["curl", "--fail", "http://localhost:9001/api/v2/version"]
HEALTHCHECK CMD ["python3", "-c", \
"import requests; requests.get('http://localhost:9001/api/v2/version').raise_for_status()"]
#======================================================================================#
# Worker #
#======================================================================================#
FROM python-base AS libretime-worker
FROM python-base as libretime-worker
WORKDIR /src
@ -189,7 +189,6 @@ RUN --mount=type=cache,target=/root/.cache/pip \
pip install --no-compile -r requirements.txt
COPY --from=python-builder /build/shared/*.whl .
COPY --from=python-builder /build/api-client/*.whl .
RUN --mount=type=cache,target=/root/.cache/pip \
pip install --no-compile *.whl && rm -Rf *.whl
@ -201,14 +200,20 @@ RUN --mount=type=cache,target=/root/.cache/pip \
USER ${UID}:${GID}
WORKDIR /app
CMD ["/usr/local/bin/libretime-worker"]
CMD ["/usr/local/bin/celery", "worker", \
"--app=libretime_worker.tasks:worker", \
"--config=libretime_worker.config", \
"--time-limit=1800", \
"--concurrency=1", \
"--loglevel=info"]
ARG LIBRETIME_VERSION
ENV LIBRETIME_VERSION=$LIBRETIME_VERSION
#======================================================================================#
# Legacy #
#======================================================================================#
FROM php:7.4-fpm AS libretime-legacy
FROM php:7.4-fpm as libretime-legacy
ENV LIBRETIME_CONFIG_FILEPATH=/etc/libretime/config.yml
ENV LIBRETIME_LOG_FILEPATH=php://stderr

View File

@ -22,15 +22,18 @@ dev-certs:
cat dev/certs/fake.{key,crt} > dev/certs/fake.pem
dev: .env dev-certs
DOCKER_BUILDKIT=1 docker compose build
docker compose run --rm legacy make build
docker compose run --rm api libretime-api migrate
docker compose up -d
DOCKER_BUILDKIT=1 docker-compose build
docker-compose run --rm legacy make build
docker-compose run --rm api libretime-api migrate
docker-compose up -d
.PHONY: VERSION
VERSION:
tools/version.sh
changelog:
tools/changelog.sh
.PHONY: tarball
tarball: VERSION
$(MAKE) -C legacy build

View File

@ -52,8 +52,23 @@ Become a financial contributor and help us sustain our community on
[Support](https://opencollective.com/libretime/contribute) this project with
your organization. Your logo will show up here with a link to your website.
<a href="https://opencollective.com/libretime">
<img src="https://opencollective.com/libretime/organizations.svg?width=890">
<a href="https://opencollective.com/libretime/organization/0/website">
<img src="https://opencollective.com/libretime/organization/0/avatar.svg">
</a>
<a href="https://opencollective.com/libretime/organization/1/website">
<img src="https://opencollective.com/libretime/organization/1/avatar.svg">
</a>
<a href="https://opencollective.com/libretime/organization/2/website">
<img src="https://opencollective.com/libretime/organization/2/avatar.svg">
</a>
<a href="https://opencollective.com/libretime/organization/3/website">
<img src="https://opencollective.com/libretime/organization/3/avatar.svg">
</a>
<a href="https://opencollective.com/libretime/organization/4/website">
<img src="https://opencollective.com/libretime/organization/4/avatar.svg">
</a>
<a href="https://opencollective.com/libretime/organization/5/website">
<img src="https://opencollective.com/libretime/organization/5/avatar.svg">
</a>
## License

1
Vagrantfile vendored
View File

@ -79,6 +79,7 @@ Vagrant.configure('2') do |config|
LIBRETIME_POSTGRESQL_PASSWORD=libretime \
LIBRETIME_RABBITMQ_PASSWORD=libretime \
bash install \
--listen-port 8080 \
--in-place \
http://192.168.10.100:8080

View File

@ -17,7 +17,6 @@ ProtectKernelTunables=true
ProtectProc=invisible
ProtectSystem=full
Environment=PYTHONOPTIMIZE=2
Environment=LIBRETIME_CONFIG_FILEPATH=@@CONFIG_FILEPATH@@
Environment=LIBRETIME_LOG_FILEPATH=@@LOG_DIR@@/analyzer.log
WorkingDirectory=@@WORKING_DIR@@/analyzer

View File

@ -36,7 +36,7 @@ def probe_replaygain(filepath: Path) -> Optional[float]:
"""
Probe replaygain will probe the given audio file and return the replaygain if available.
"""
cmd = _ffprobe("-i", filepath, errors="backslashreplace")
cmd = _ffprobe("-i", filepath)
track_gain_match = _PROBE_REPLAYGAIN_RE.search(cmd.stderr)
@ -75,7 +75,8 @@ def compute_silences(filepath: Path) -> List[Tuple[float, float]]:
cmd = _ffmpeg(
*("-i", filepath),
"-vn",
*("-filter", "highpass=frequency=80,silencedetect=noise=-60dB:duration=0.9"),
*("-filter", "highpass=frequency=1000"),
*("-filter", "silencedetect=noise=0.15:duration=1"),
)
starts, ends = [], []

View File

@ -5,24 +5,10 @@ from typing import Any, Dict
import mutagen
from libretime_shared.files import compute_md5
from mutagen.easyid3 import EasyID3
logger = logging.getLogger(__name__)
def flatten(xss):
return [x for xs in xss for x in xs]
def comment_get(id3, _):
comments = [v.text for k, v in id3.items() if "COMM" in k or "comment" in k]
return flatten(comments)
EasyID3.RegisterKey("comment", comment_get)
def analyze_metadata(filepath_: str, metadata: Dict[str, Any]):
"""
Extract audio metadata from tags embedded in the file using mutagen.
@ -85,36 +71,34 @@ def analyze_metadata(filepath_: str, metadata: Dict[str, Any]):
except (AttributeError, KeyError, IndexError):
pass
extracted_tags_mapping = [
("title", "track_title"),
("artist", "artist_name"),
("album", "album_title"),
("bpm", "bpm"),
("composer", "composer"),
("conductor", "conductor"),
("copyright", "copyright"),
("comment", "comment"),
("comment", "comments"),
("comment", "description"),
("encoded_by", "encoder"),
("genre", "genre"),
("isrc", "isrc"),
("label", "label"),
("organization", "label"),
# ("length", "length"),
("language", "language"),
("last_modified", "last_modified"),
("mood", "mood"),
("bit_rate", "bit_rate"),
("replay_gain", "replaygain"),
# ("tracknumber", "track_number"),
# ("track_total", "track_total"),
("website", "website"),
("date", "year"),
# ("mime_type", "mime"),
]
extracted_tags_mapping = {
"title": "track_title",
"artist": "artist_name",
"album": "album_title",
"bpm": "bpm",
"composer": "composer",
"conductor": "conductor",
"copyright": "copyright",
"comment": "comment",
"encoded_by": "encoder",
"genre": "genre",
"isrc": "isrc",
"label": "label",
"organization": "label",
# "length": "length",
"language": "language",
"last_modified": "last_modified",
"mood": "mood",
"bit_rate": "bit_rate",
"replay_gain": "replaygain",
# "tracknumber": "track_number",
# "track_total": "track_total",
"website": "website",
"date": "year",
# "mime_type": "mime",
}
for extracted_key, metadata_key in extracted_tags_mapping:
for extracted_key, metadata_key in extracted_tags_mapping.items():
try:
metadata[metadata_key] = extracted[extracted_key]
if isinstance(metadata[metadata_key], list):

View File

@ -16,7 +16,8 @@ logger = logging.getLogger(__name__)
class Step(Protocol):
@staticmethod
def __call__(filename: str, metadata: Dict[str, Any]): ...
def __call__(filename: str, metadata: Dict[str, Any]):
...
class PipelineStatus(int, Enum):

View File

@ -2,5 +2,5 @@
# This file is auto-generated by tools/extract_requirements.py.
mutagen>=1.45.1,<1.48
pika>=1.0.0,<1.4
requests>=2.32.2,<2.33
requests>=2.31.0,<2.32
typing_extensions

View File

@ -1,10 +1,8 @@
from setuptools import find_packages, setup
version = "4.2.0" # x-release-please-version
setup(
name="libretime-analyzer",
version=version,
version="3.2.0",
description="Libretime Analyzer",
author="LibreTime Contributors",
url="https://github.com/libretime/libretime",
@ -24,7 +22,7 @@ setup(
install_requires=[
"mutagen>=1.45.1,<1.48",
"pika>=1.0.0,<1.4",
"requests>=2.32.2,<2.33",
"requests>=2.31.0,<2.32",
"typing_extensions",
],
extras_require={

View File

@ -23,28 +23,28 @@ FILES = [
# 8s -> 9s: silence
# 9s -> 12s: musik
# 12s -> 15s: pink noise fade out
Fixture(here / "s1-jointstereo.mp3", 15.0, 1.4, 15.0, -5.9 ),
Fixture(here / "s1-mono.mp3", 15.0, 1.5, 15.0, -2.0 ),
Fixture(here / "s1-stereo.mp3", 15.0, 1.4, 15.0, -5.9 ),
Fixture(here / "s1-mono-12.mp3", 15.0, 1.2, 15.0, +7.0 ),
Fixture(here / "s1-stereo-12.mp3", 15.0, 1.2, 15.0, +6.1 ),
Fixture(here / "s1-mono+12.mp3", 15.0, 1.2, 15.0, -17.0 ),
Fixture(here / "s1-stereo+12.mp3", 15.0, 1.2, 15.0, -17.8 ),
Fixture(here / "s1-mono.flac", 15.0, 1.4, 15.0, -2.3 ),
Fixture(here / "s1-stereo.flac", 15.0, 1.4, 15.0, -6.0 ),
Fixture(here / "s1-mono-12.flac", 15.0, 2.0, 15.0, +10.0 ),
Fixture(here / "s1-stereo-12.flac", 15.0, 1.8, 15.0, +5.9 ),
Fixture(here / "s1-mono+12.flac", 15.0, 0.0, 15.0, -12.0 ),
Fixture(here / "s1-stereo+12.flac", 15.0, 0.0, 15.0, -14.9 ),
Fixture(here / "s1-mono.m4a", 15.0, 1.4, 15.0, -4.5 ),
Fixture(here / "s1-stereo.m4a", 15.0, 1.4, 15.0, -5.8 ),
Fixture(here / "s1-mono.ogg", 15.0, 1.4, 15.0, -4.9 ),
Fixture(here / "s1-stereo.ogg", 15.0, 1.4, 15.0, -5.7 ),
Fixture(here / "s1-stereo", 15.0, 1.4, 15.0, -5.7 ),
Fixture(here / "s1-mono.wav", 15.0, 1.5, 15.0, -2.3 ),
Fixture(here / "s1-stereo.wav", 15.0, 1.4, 15.0, -6.0 ),
Fixture(here / "s1-jointstereo.mp3", 15.0, 6.0, 13.0, -5.9 ),
Fixture(here / "s1-mono.mp3", 15.0, 6.0, 13.0, -2.0 ),
Fixture(here / "s1-stereo.mp3", 15.0, 6.0, 13.0, -5.9 ),
Fixture(here / "s1-mono-12.mp3", 15.0, 9.0, 12.0, +7.0 ),
Fixture(here / "s1-stereo-12.mp3", 15.0, 9.0, 12.0, +6.1 ),
Fixture(here / "s1-mono+12.mp3", 15.0, 3.5, 13.0, -17.0 ),
Fixture(here / "s1-stereo+12.mp3", 15.0, 3.5, 13.0, -17.8 ),
Fixture(here / "s1-mono.flac", 15.0, 6.0, 13.0, -2.3 ),
Fixture(here / "s1-stereo.flac", 15.0, 6.0, 13.0, -6.0 ),
Fixture(here / "s1-mono-12.flac", 15.0, 9.0, 12.0, +10.0 ),
Fixture(here / "s1-stereo-12.flac", 15.0, 9.0, 12.0, +5.9 ),
Fixture(here / "s1-mono+12.flac", 15.0, 3.5, 13.0, -12.0 ),
Fixture(here / "s1-stereo+12.flac", 15.0, 3.5, 13.0, -14.9 ),
Fixture(here / "s1-mono.m4a", 15.0, 6.0, 13.0, -4.5 ),
Fixture(here / "s1-stereo.m4a", 15.0, 6.0, 13.0, -5.8 ),
Fixture(here / "s1-mono.ogg", 15.0, 6.0, 13.0, -4.9 ),
Fixture(here / "s1-stereo.ogg", 15.0, 6.0, 13.0, -5.7 ),
Fixture(here / "s1-stereo", 15.0, 6.0, 13.0, -5.7 ),
Fixture(here / "s1-mono.wav", 15.0, 6.0, 13.0, -2.3 ),
Fixture(here / "s1-stereo.wav", 15.0, 6.0, 13.0, -6.0 ),
# sample 1 large (looped for 2 hours)
Fixture(here / "s1-large.flac", 7200, 1.4, 7200, -6.0 ),
Fixture(here / "s1-large.flac", 7200, 6.0, 7198, -6.0 ),
# sample 2
# 0s -> 1.8s: silence
# 1.8s : noise
@ -96,18 +96,12 @@ tags = {
"comment": "Test Comment",
}
mp3Tags = {
**tags,
"comments": tags["comment"],
"description": tags["comment"],
}
FILES_TAGGED = [
FixtureMeta(
here / "s1-jointstereo-tagged.mp3",
{
**meta,
**mp3Tags,
**tags,
"bit_rate": approx(128000, abs=1e2),
"channels": 2,
"mime": "audio/mp3",
@ -117,7 +111,7 @@ FILES_TAGGED = [
here / "s1-mono-tagged.mp3",
{
**meta,
**mp3Tags,
**tags,
"bit_rate": approx(64000, abs=1e2),
"channels": 1,
"mime": "audio/mp3",
@ -127,7 +121,7 @@ FILES_TAGGED = [
here / "s1-stereo-tagged.mp3",
{
**meta,
**mp3Tags,
**tags,
"bit_rate": approx(128000, abs=1e2),
"channels": 2,
"mime": "audio/mp3",
@ -157,7 +151,7 @@ FILES_TAGGED = [
here / "s1-mono-tagged.m4a",
{
**meta,
**mp3Tags,
**tags,
"bit_rate": approx(65000, abs=5e4),
"channels": 2, # Weird
"mime": "audio/mp4",
@ -167,7 +161,7 @@ FILES_TAGGED = [
here / "s1-stereo-tagged.m4a",
{
**meta,
**mp3Tags,
**tags,
"bit_rate": approx(128000, abs=1e5),
"channels": 2,
"mime": "audio/mp4",
@ -234,18 +228,12 @@ tags = {
"comment": "Ł Ą Ż Ę Ć Ń Ś Ź",
}
mp3Tags = {
**tags,
"comments": tags["comment"],
"description": tags["comment"],
}
FILES_TAGGED += [
FixtureMeta(
here / "s1-jointstereo-tagged-utf8.mp3",
{
**meta,
**mp3Tags,
**tags,
"bit_rate": approx(128000, abs=1e2),
"channels": 2,
"mime": "audio/mp3",
@ -255,7 +243,7 @@ FILES_TAGGED += [
here / "s1-mono-tagged-utf8.mp3",
{
**meta,
**mp3Tags,
**tags,
"bit_rate": approx(64000, abs=1e2),
"channels": 1,
"mime": "audio/mp3",
@ -265,7 +253,7 @@ FILES_TAGGED += [
here / "s1-stereo-tagged-utf8.mp3",
{
**meta,
**mp3Tags,
**tags,
"bit_rate": approx(128000, abs=1e2),
"channels": 2,
"mime": "audio/mp3",
@ -295,7 +283,7 @@ FILES_TAGGED += [
here / "s1-mono-tagged-utf8.m4a",
{
**meta,
**mp3Tags,
**tags,
"bit_rate": approx(65000, abs=5e4),
"channels": 2, # Weird
"mime": "audio/mp4",
@ -305,7 +293,7 @@ FILES_TAGGED += [
here / "s1-stereo-tagged-utf8.m4a",
{
**meta,
**mp3Tags,
**tags,
"bit_rate": approx(128000, abs=1e5),
"channels": 2,
"mime": "audio/mp4",

View File

@ -27,8 +27,8 @@ def test_analyze_metadata(filepath: Path, metadata: dict):
del metadata["length"]
del found["length"]
# ogg,flac files does not support comments yet
if not filepath.suffix == ".m4a" and not filepath.suffix == ".mp3":
# mp3,ogg,flac files does not support comments yet
if not filepath.suffix == ".m4a":
if "comment" in metadata:
del metadata["comment"]

View File

@ -214,6 +214,3 @@ class ApiClient:
def update_metadata_on_tunein(self):
self._base_client.update_metadata_on_tunein()
def trigger_task_manager(self):
self._base_client.version()

View File

@ -1,4 +1,4 @@
# Please do not edit this file, edit the setup.py file!
# This file is auto-generated by tools/extract_requirements.py.
python-dateutil>=2.8.1,<2.10
requests>=2.32.2,<2.33
python-dateutil>=2.8.1,<2.9
requests>=2.31.0,<2.32

View File

@ -1,10 +1,8 @@
from setuptools import find_packages, setup
version = "4.2.0" # x-release-please-version
setup(
name="libretime-api-client",
version=version,
version="3.2.0",
description="LibreTime API Client",
author="LibreTime Contributors",
url="https://github.com/libretime/libretime",
@ -18,8 +16,8 @@ setup(
package_data={"": ["py.typed"]},
python_requires=">=3.8",
install_requires=[
"python-dateutil>=2.8.1,<2.10",
"requests>=2.32.2,<2.33",
"python-dateutil>=2.8.1,<2.9",
"requests>=2.31.0,<2.32",
],
extras_require={
"dev": [

View File

@ -18,7 +18,6 @@ ProtectKernelTunables=true
ProtectProc=invisible
ProtectSystem=full
Environment=PYTHONOPTIMIZE=2
Environment=LIBRETIME_CONFIG_FILEPATH=@@CONFIG_FILEPATH@@
Environment=LIBRETIME_LOG_FILEPATH=@@LOG_DIR@@/api.log
@ -26,7 +25,7 @@ Type=notify
KillMode=mixed
ExecStart=@@VENV_DIR@@/bin/gunicorn \
--workers 4 \
--worker-class libretime_api.gunicorn.Worker \
--worker-class uvicorn.workers.UvicornWorker \
--log-file - \
--bind unix:/run/libretime-api.sock \
libretime_api.asgi

View File

@ -18,13 +18,12 @@ class StreamPreferences(BaseModel):
input_fade_transition: float
message_format: MessageFormatKind
message_offline: str
replay_gain_enabled: bool
replay_gain_offset: float
# input_auto_switch_off: bool
# input_auto_switch_on: bool
# input_main_user: str
# input_main_password: str
# replay_gain_enabled: bool
# replay_gain_offset: float
# track_fade_in: float
# track_fade_out: float
# track_fade_transition: float
@ -83,8 +82,6 @@ class Preference(models.Model):
int(entries.get("stream_label_format") or 0)
),
message_offline=entries.get("off_air_meta") or "Offline",
replay_gain_enabled=entries.get("enable_replay_gain") == "1",
replay_gain_offset=float(entries.get("replay_gain_modifier") or 0.0),
)
@classmethod

View File

@ -8,7 +8,6 @@ from .role import Role
class UserManager(BaseUserManager):
# pylint: disable=too-many-positional-arguments
def create_user(self, role, username, password, email, first_name, last_name):
user = self.model(
role=role,
@ -21,7 +20,6 @@ class UserManager(BaseUserManager):
user.save(using=self._db)
return user
# pylint: disable=too-many-positional-arguments
def create_superuser(self, username, password, email, first_name, last_name):
return self.create_user(
Role.ADMIN,

View File

@ -6,8 +6,6 @@ class StreamPreferencesSerializer(serializers.Serializer):
input_fade_transition = serializers.FloatField(read_only=True)
message_format = serializers.IntegerField(read_only=True)
message_offline = serializers.CharField(read_only=True)
replay_gain_enabled = serializers.BooleanField(read_only=True)
replay_gain_offset = serializers.FloatField(read_only=True)
# pylint: disable=abstract-method

View File

@ -4,7 +4,7 @@ from libretime_api.core.models.preference import Preference
# pylint: disable=invalid-name,unused-argument
def test_preference_get_site_preferences(db):
result = Preference.get_site_preferences()
assert result.model_dump() == {
assert result.dict() == {
"station_name": "LibreTime",
}
@ -12,19 +12,17 @@ def test_preference_get_site_preferences(db):
# pylint: disable=invalid-name,unused-argument
def test_preference_get_stream_preferences(db):
result = Preference.get_stream_preferences()
assert result.model_dump() == {
assert result.dict() == {
"input_fade_transition": 0.0,
"message_format": 0,
"message_offline": "LibreTime - offline",
"replay_gain_enabled": True,
"replay_gain_offset": 0.0,
}
# pylint: disable=invalid-name,unused-argument
def test_preference_get_stream_state(db):
result = Preference.get_stream_state()
assert result.model_dump() == {
assert result.dict() == {
"input_main_connected": False,
"input_main_streaming": False,
"input_show_connected": False,

View File

@ -9,8 +9,6 @@ def test_stream_preferences_get(db, api_client: APIClient):
"input_fade_transition": 0.0,
"message_format": 0,
"message_offline": "LibreTime - offline",
"replay_gain_enabled": True,
"replay_gain_offset": 0.0,
}

View File

@ -22,7 +22,7 @@ class InfoView(APIView):
def get(self, request):
data = Preference.get_site_preferences()
return Response(
data.model_dump(
data.dict(
include={
"station_name",
}

View File

@ -14,13 +14,11 @@ class StreamPreferencesView(views.APIView):
def get(self, request):
data = Preference.get_stream_preferences()
return Response(
data.model_dump(
data.dict(
include={
"input_fade_transition",
"message_format",
"message_offline",
"replay_gain_enabled",
"replay_gain_offset",
}
)
)
@ -34,7 +32,7 @@ class StreamStateView(views.APIView):
def get(self, request):
data = Preference.get_stream_state()
return Response(
data.model_dump(
data.dict(
include={
"input_main_connected",
"input_main_streaming",

View File

@ -1,5 +0,0 @@
from uvicorn.workers import UvicornWorker # pylint: disable=import-error
class Worker(UvicornWorker):
CONFIG_KWARGS = {"lifespan": "off"}

View File

@ -8,6 +8,8 @@ UP = """
-- DELETE FROM cc_pref WHERE keystr = 'system_version';
-- INSERT INTO cc_pref (keystr, valstr) VALUES ('system_version', '2.5.5');
ALTER TABLE cc_show ADD COLUMN image_path varchar(255) DEFAULT '';
ALTER TABLE cc_show_instances ADD COLUMN description varchar(255) DEFAULT '';
"""
DOWN = None

View File

@ -5,7 +5,7 @@ from django.db import migrations
from ._migrations import legacy_migration_factory
UP = """
ALTER TABLE cc_files ADD COLUMN artwork VARCHAR(255);
ALTER TABLE cc_files ADD COLUMN artwork TYPE character varying(255);
"""
DOWN = None

View File

@ -4,18 +4,12 @@ from django.db import migrations
from ._migrations import legacy_migration_factory
# This migration is currently a placeholder for 3.0.0-alpha.9.1.
# Please do not remove it. There are currently no actions, but it
# needs to remain intact so it does not fail when called from the
# migrations script. Any future migrations that may apply to
# 3.0.0-alpha.9.1 will be added to this file.
UP = """
ALTER TABLE cc_files ADD COLUMN artwork VARCHAR(4096);
"""
DOWN = """
ALTER TABLE cc_files DROP COLUMN IF EXISTS artwork;
"""

View File

@ -1,37 +0,0 @@
# pylint: disable=invalid-name
from django.db import migrations
from ._migrations import legacy_migration_factory
UP = """
ALTER TABLE cc_show ADD COLUMN override_intro_playlist boolean default 'f' NOT NULL;
ALTER TABLE cc_show ADD COLUMN intro_playlist_id integer DEFAULT NULL;
ALTER TABLE cc_show ADD CONSTRAINT cc_playlist_intro_playlist_fkey FOREIGN KEY (intro_playlist_id) REFERENCES cc_playlist (id) ON DELETE SET NULL;
ALTER TABLE cc_show ADD COLUMN override_outro_playlist boolean default 'f' NOT NULL;
ALTER TABLE cc_show ADD COLUMN outro_playlist_id integer DEFAULT NULL;
ALTER TABLE cc_show ADD CONSTRAINT cc_playlist_outro_playlist_fkey FOREIGN KEY (outro_playlist_id) REFERENCES cc_playlist (id) ON DELETE SET NULL;
"""
DOWN = """
ALTER TABLE cc_show DROP COLUMN IF EXISTS override_intro_playlist;
ALTER TABLE cc_show DROP COLUMN IF EXISTS intro_playlist_id;
ALTER TABLE cc_show DROP CONSTRAINT IF EXISTS cc_playlist_intro_playlist_fkey;
ALTER TABLE cc_show DROP COLUMN IF EXISTS override_outro_playlist;
ALTER TABLE cc_show DROP COLUMN IF EXISTS outro_playlist_id;
ALTER TABLE cc_show DROP CONSTRAINT IF EXISTS cc_playlist_outro_playlist_fkey;
"""
class Migration(migrations.Migration):
dependencies = [
("legacy", "0045_add_sessions_table"),
]
operations = [
migrations.RunPython(
code=legacy_migration_factory(
target="46",
sql=UP,
)
)
]

View File

@ -1,2 +1,2 @@
# The schema version is defined using the migration file prefix number
LEGACY_SCHEMA_VERSION = "46"
LEGACY_SCHEMA_VERSION = "45"

View File

@ -11,28 +11,15 @@ def get_schema_version():
Don't use django models as they might break in the future. Our concern is to upgrade
the legacy database schema to the point where django is in charge of the migrations.
An airtime 2.5.1 migration will not have schema_version, in that case, we look for
system_version to have a value of 2.5.1 and return that as the schema version value
(really just needs to be anything besides None, so that the next migration doesn't overwrite
the database)
"""
if "cc_pref" not in connection.introspection.table_names():
return None
with connection.cursor() as cursor:
cursor.execute(
"""
SELECT valstr AS version
FROM cc_pref
WHERE (keystr = 'schema_version') OR (keystr = 'system_version' AND valstr = '2.5.1')
"""
)
cursor.execute("SELECT valstr FROM cc_pref WHERE keystr = 'schema_version'")
row = cursor.fetchone()
if row and row[0]:
return row[0]
return None
return row[0] if row else None
def set_schema_version(cursor, version: str):

View File

@ -126,10 +126,6 @@ CREATE TABLE "cc_show"
"has_autoplaylist" BOOLEAN DEFAULT 'f' NOT NULL,
"autoplaylist_id" INTEGER,
"autoplaylist_repeat" BOOLEAN DEFAULT 'f' NOT NULL,
"override_intro_playlist" BOOLEAN DEFAULT 'f' NOT NULL,
"intro_playlist_id" INTEGER,
"override_outro_playlist" BOOLEAN DEFAULT 'f' NOT NULL,
"outro_playlist_id" INTEGER,
PRIMARY KEY ("id")
);
@ -722,16 +718,6 @@ ALTER TABLE "cc_show" ADD CONSTRAINT "cc_playlist_autoplaylist_fkey"
REFERENCES "cc_playlist" ("id")
ON DELETE SET NULL;
ALTER TABLE "cc_show" ADD CONSTRAINT "cc_playlist_intro_playlist_fkey"
FOREIGN KEY ("intro_playlist_id")
REFERENCES "cc_playlist" ("id")
ON DELETE SET NULL;
ALTER TABLE "cc_show" ADD CONSTRAINT "cc_playlist_outro_playlist_fkey"
FOREIGN KEY ("outro_playlist_id")
REFERENCES "cc_playlist" ("id")
ON DELETE SET NULL;
ALTER TABLE "cc_show_instances" ADD CONSTRAINT "cc_show_fkey"
FOREIGN KEY ("show_id")
REFERENCES "cc_show" ("id")

View File

@ -1 +0,0 @@
from .readwriteserializer import ReadWriteSerializerMixin

View File

@ -1,33 +0,0 @@
from rest_framework.serializers import Serializer
class ReadWriteSerializerMixin:
"""
Overrides get_serializer_class to choose the read serializer
for GET requests and the write serializer for POST requests.
Set read_serializer_class and write_serializer_class attributes on a
viewset.
"""
read_serializer_class = Serializer
write_serializer_class = Serializer
def get_serializer_class(self):
if self.action in ["create"]:
return self.get_write_serializer_class()
return self.get_read_serializer_class()
def get_read_serializer_class(self):
assert self.read_serializer_class is not None, (
f"'{self.__class__.__name__}' should either include a `read_serializer_class`"
"attribute, or override the `get_read_serializer_class()` method."
)
return self.read_serializer_class
def get_write_serializer_class(self):
assert self.write_serializer_class is not None, (
f"'{self.__class__.__name__}' should either include a `write_serializer_class`"
"attribute, or override the `get_write_serializer_class()` method."
)
return self.write_serializer_class

View File

@ -1,4 +1,4 @@
from .core.models.role import Role
from .core.models import Role
GUEST_PERMISSIONS = [
"view_schedule",

View File

@ -1,5 +1,4 @@
from django.db import models
from django.utils.timezone import now
class Schedule(models.Model):
@ -116,14 +115,6 @@ class Schedule(models.Model):
return self.instance.ends_at
return self.ends_at
@staticmethod
def is_file_scheduled_in_the_future(file_id):
count = Schedule.objects.filter(
file_id=file_id,
ends_at__gt=now(),
).count()
return count > 0
class Meta:
managed = False
db_table = "cc_schedule"

View File

@ -69,29 +69,7 @@ class Show(models.Model):
auto_playlist_enabled = models.BooleanField(db_column="has_autoplaylist")
auto_playlist_repeat = models.BooleanField(db_column="autoplaylist_repeat")
intro_playlist = models.ForeignKey(
"schedule.Playlist",
on_delete=models.DO_NOTHING,
blank=True,
null=True,
db_column="intro_playlist_id",
related_name="intro_playlist",
)
override_intro_playlist = models.BooleanField(db_column="override_intro_playlist")
outro_playlist = models.ForeignKey(
"schedule.Playlist",
on_delete=models.DO_NOTHING,
blank=True,
null=True,
db_column="outro_playlist_id",
related_name="outro_playlist",
)
override_outro_playlist = models.BooleanField(db_column="override_outro_playlist")
hosts = models.ManyToManyField( # type: ignore[var-annotated]
hosts = models.ManyToManyField(
"core.User",
through="ShowHost",
)

View File

@ -1,5 +1,5 @@
from .playlist import PlaylistContentSerializer, PlaylistSerializer
from .schedule import ReadScheduleSerializer, WriteScheduleSerializer
from .schedule import ScheduleSerializer
from .show import (
ShowDaysSerializer,
ShowHostSerializer,

View File

@ -3,17 +3,10 @@ from rest_framework import serializers
from ..models import Schedule
class ReadScheduleSerializer(serializers.ModelSerializer):
class ScheduleSerializer(serializers.ModelSerializer):
cue_out = serializers.DurationField(source="get_cue_out", read_only=True)
ends_at = serializers.DateTimeField(source="get_ends_at", read_only=True)
class Meta:
model = Schedule
fields = "__all__"
class WriteScheduleSerializer(serializers.ModelSerializer):
class Meta:
model = Schedule
fields = "__all__"

View File

@ -21,10 +21,6 @@ class ShowSerializer(serializers.ModelSerializer):
"auto_playlist",
"auto_playlist_enabled",
"auto_playlist_repeat",
"intro_playlist",
"override_intro_playlist",
"outro_playlist",
"override_outro_playlist",
]

View File

@ -2,9 +2,8 @@ from django.db import models
from django_filters import rest_framework as filters
from rest_framework import viewsets
from ...mixins import ReadWriteSerializerMixin
from ..models import Schedule
from ..serializers import ReadScheduleSerializer, WriteScheduleSerializer
from ..serializers import ScheduleSerializer
class ScheduleFilter(filters.FilterSet):
@ -27,9 +26,8 @@ class ScheduleFilter(filters.FilterSet):
fields = [] # type: ignore
class ScheduleViewSet(ReadWriteSerializerMixin, viewsets.ModelViewSet):
class ScheduleViewSet(viewsets.ModelViewSet):
queryset = Schedule.objects.all()
read_serializer_class = ReadScheduleSerializer
write_serializer_class = WriteScheduleSerializer
serializer_class = ScheduleSerializer
filterset_class = ScheduleFilter
model_permission_name = "schedule"

View File

@ -1,4 +1,5 @@
from os import getenv
from warnings import warn
# pylint: disable=unused-import
from ._internal import (
@ -24,7 +25,15 @@ LIBRETIME_CONFIG_FILEPATH = getenv("LIBRETIME_CONFIG_FILEPATH")
CONFIG = Config(LIBRETIME_CONFIG_FILEPATH) # type: ignore[arg-type, misc]
SECRET_KEY = CONFIG.general.secret_key
if CONFIG.general.secret_key is None:
warn(
"The [general.secret_key] configuration field is not set but will be required "
"in the next major release. Using [general.api_key] as fallback.",
FutureWarning,
)
SECRET_KEY = CONFIG.general.api_key
else:
SECRET_KEY = CONFIG.general.secret_key
ALLOWED_HOSTS = ["*"]

View File

@ -1,96 +1,35 @@
import os
from unittest.mock import patch
from django.conf import settings
from model_bakery import baker
from rest_framework.test import APITestCase
from ...._fixtures import AUDIO_FILENAME
from ...models import File
class TestFileViewSet(APITestCase):
@classmethod
def setUpTestData(cls):
cls.path = "/api/v2/files/{id}/download"
cls.token = settings.CONFIG.general.api_key
def test_download_invalid(self):
def test_invalid(self):
path = self.path.format(id="a")
self.client.credentials(HTTP_AUTHORIZATION=f"Api-Key {self.token}")
file_id = "1"
response = self.client.get(f"/api/v2/files/{file_id}/download")
response = self.client.get(path)
self.assertEqual(response.status_code, 400)
def test_does_not_exist(self):
path = self.path.format(id="1")
self.client.credentials(HTTP_AUTHORIZATION=f"Api-Key {self.token}")
response = self.client.get(path)
self.assertEqual(response.status_code, 404)
def test_download(self):
self.client.credentials(HTTP_AUTHORIZATION=f"Api-Key {self.token}")
file: File = baker.make(
def test_exists(self):
file = baker.make(
"storage.File",
mime="audio/mp3",
filepath=AUDIO_FILENAME,
)
response = self.client.get(f"/api/v2/files/{file.id}/download")
path = self.path.format(id=str(file.pk))
self.client.credentials(HTTP_AUTHORIZATION=f"Api-Key {self.token}")
response = self.client.get(path)
self.assertEqual(response.status_code, 200)
def test_destroy(self):
self.client.credentials(HTTP_AUTHORIZATION=f"Api-Key {self.token}")
file: File = baker.make(
"storage.File",
mime="audio/mp3",
filepath=AUDIO_FILENAME,
)
with patch("libretime_api.storage.views.file.remove") as remove_mock:
response = self.client.delete(f"/api/v2/files/{file.id}")
self.assertEqual(response.status_code, 204)
remove_mock.assert_called_with(
os.path.join(settings.CONFIG.storage.path, file.filepath)
)
def test_destroy_no_file(self):
self.client.credentials(HTTP_AUTHORIZATION=f"Api-Key {self.token}")
file = baker.make(
"storage.File",
mime="audio/mp3",
filepath="invalid.mp3",
)
response = self.client.delete(f"/api/v2/files/{file.id}")
self.assertEqual(response.status_code, 204)
def test_destroy_invalid(self):
self.client.credentials(HTTP_AUTHORIZATION=f"Api-Key {self.token}")
file_id = "1"
response = self.client.delete(f"/api/v2/files/{file_id}")
self.assertEqual(response.status_code, 404)
def test_filters(self):
file = baker.make(
"storage.File",
mime="audio/mp3",
filepath=AUDIO_FILENAME,
genre="Soul",
md5="5a11ffe0e6c6d70fcdbad1b734be6482",
)
baker.make(
"storage.File",
mime="audio/mp3",
filepath=AUDIO_FILENAME,
genre="R&B",
md5="5a11ffe0e6c6d70fcdbad1b734be6483",
)
self.client.credentials(HTTP_AUTHORIZATION=f"Api-Key {self.token}")
path = "/api/v2/files"
results = self.client.get(path).json()
self.assertEqual(len(results), 2)
path = f"/api/v2/files?md5={file.md5}"
results = self.client.get(path).json()
self.assertEqual(len(results), 1)
path = "/api/v2/files?genre=Soul"
results = self.client.get(path).json()
self.assertEqual(len(results), 1)
path = "/api/v2/files?genre=R%26B"
results = self.client.get(path).json()
self.assertEqual(len(results), 1)

View File

@ -1,62 +1,25 @@
import logging
import os
from os import remove
from django.conf import settings
from django.http import HttpResponse
from django.utils.encoding import filepath_to_uri
from django_filters import rest_framework as filters
from rest_framework import status, viewsets
from django.http import FileResponse
from django.shortcuts import get_object_or_404
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import APIException
from rest_framework.serializers import IntegerField
from ...schedule.models import Schedule
from ..models import File
from ..serializers import FileSerializer
logger = logging.getLogger(__name__)
class FileInUse(APIException):
status_code = status.HTTP_409_CONFLICT
default_detail = "The file is currently used"
default_code = "file_in_use"
class FileViewSet(viewsets.ModelViewSet):
queryset = File.objects.all()
serializer_class = FileSerializer
model_permission_name = "file"
filter_backends = (filters.DjangoFilterBackend,)
filterset_fields = ("md5", "genre")
# pylint: disable=invalid-name,unused-argument
@action(detail=True, methods=["GET"])
def download(self, request, pk=None):
instance: File = self.get_object()
def download(self, request, pk=None): # pylint: disable=invalid-name
pk = IntegerField().to_internal_value(data=pk)
response = HttpResponse()
# HTTP headers must be USASCII encoded, or Nginx might not find the file and
# will return a 404.
redirect_uri = filepath_to_uri(os.path.join("/api/_media", instance.filepath))
response["X-Accel-Redirect"] = redirect_uri
return response
def perform_destroy(self, instance: File):
if Schedule.is_file_scheduled_in_the_future(file_id=instance.id):
raise FileInUse("file is scheduled in the future")
try:
if instance.filepath is None:
logger.warning("file does not have a filepath: %d", instance.id)
return
path = os.path.join(settings.CONFIG.storage.path, instance.filepath)
if not os.path.isfile(path):
logger.warning("file does not exist in storage: %d", instance.id)
return
remove(path)
except OSError as exception:
raise APIException("could not delete file from storage") from exception
file = get_object_or_404(File, pk=pk)
path = os.path.join(settings.CONFIG.storage.path, file.filepath)
return FileResponse(open(path, "rb"), content_type=file.mime)

View File

@ -4,7 +4,6 @@ URL Configuration
For more information on this file, see
https://docs.djangoproject.com/en/3.2/topics/http/urls/
"""
from django.urls import include, path
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView

View File

@ -10,7 +10,6 @@ django-settings-module = "libretime_api.settings.testing"
[tool.pylint.messages_control]
extension-pkg-whitelist = "pydantic"
disable = [
"duplicate-code",
"fixme",
"missing-class-docstring",
"missing-function-docstring",

View File

@ -1,11 +1,11 @@
# Please do not edit this file, edit the setup.py file!
# This file is auto-generated by tools/extract_requirements.py.
django-cors-headers>=3.14.0,<4.5
django-filter>=2.4.0,<24.4
django-cors-headers>=3.14.0,<4.4
django-filter>=2.4.0,<23.4
django>=4.2.0,<4.3
djangorestframework>=3.14.0,<3.16
drf-spectacular>=0.22.1,<0.29
gunicorn>=22.0.0,<23.1
djangorestframework>=3.14.0,<3.15
drf-spectacular>=0.22.1,<0.27
gunicorn>=20.1.0,<21.3
psycopg[c]>=3.1.8,<3.2
requests>=2.32.2,<2.33
uvicorn[standard]>=0.17.6,<0.33.0
requests>=2.31.0,<2.32
uvicorn[standard]>=0.17.6,<0.24.0

View File

@ -154,15 +154,6 @@ paths:
/api/v2/files:
get:
operationId: files_list
parameters:
- in: query
name: genre
schema:
type: string
- in: query
name: md5
schema:
type: string
tags:
- files
security:
@ -2561,12 +2552,6 @@ paths:
/api/v2/schedule:
get:
operationId: schedule_list
description: |-
Overrides get_serializer_class to choose the read serializer
for GET requests and the write serializer for POST requests.
Set read_serializer_class and write_serializer_class attributes on a
viewset.
parameters:
- in: query
name: broadcasted
@ -2612,29 +2597,23 @@ paths:
schema:
type: array
items:
$ref: "#/components/schemas/ReadSchedule"
$ref: "#/components/schemas/Schedule"
description: ""
post:
operationId: schedule_create
description: |-
Overrides get_serializer_class to choose the read serializer
for GET requests and the write serializer for POST requests.
Set read_serializer_class and write_serializer_class attributes on a
viewset.
tags:
- schedule
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/WriteSchedule"
$ref: "#/components/schemas/Schedule"
application/x-www-form-urlencoded:
schema:
$ref: "#/components/schemas/WriteSchedule"
$ref: "#/components/schemas/Schedule"
multipart/form-data:
schema:
$ref: "#/components/schemas/WriteSchedule"
$ref: "#/components/schemas/Schedule"
required: true
security:
- cookieAuth: []
@ -2644,17 +2623,11 @@ paths:
content:
application/json:
schema:
$ref: "#/components/schemas/WriteSchedule"
$ref: "#/components/schemas/Schedule"
description: ""
/api/v2/schedule/{id}:
get:
operationId: schedule_retrieve
description: |-
Overrides get_serializer_class to choose the read serializer
for GET requests and the write serializer for POST requests.
Set read_serializer_class and write_serializer_class attributes on a
viewset.
parameters:
- in: path
name: id
@ -2672,16 +2645,10 @@ paths:
content:
application/json:
schema:
$ref: "#/components/schemas/ReadSchedule"
$ref: "#/components/schemas/Schedule"
description: ""
put:
operationId: schedule_update
description: |-
Overrides get_serializer_class to choose the read serializer
for GET requests and the write serializer for POST requests.
Set read_serializer_class and write_serializer_class attributes on a
viewset.
parameters:
- in: path
name: id
@ -2695,13 +2662,13 @@ paths:
content:
application/json:
schema:
$ref: "#/components/schemas/ReadSchedule"
$ref: "#/components/schemas/Schedule"
application/x-www-form-urlencoded:
schema:
$ref: "#/components/schemas/ReadSchedule"
$ref: "#/components/schemas/Schedule"
multipart/form-data:
schema:
$ref: "#/components/schemas/ReadSchedule"
$ref: "#/components/schemas/Schedule"
required: true
security:
- cookieAuth: []
@ -2711,16 +2678,10 @@ paths:
content:
application/json:
schema:
$ref: "#/components/schemas/ReadSchedule"
$ref: "#/components/schemas/Schedule"
description: ""
patch:
operationId: schedule_partial_update
description: |-
Overrides get_serializer_class to choose the read serializer
for GET requests and the write serializer for POST requests.
Set read_serializer_class and write_serializer_class attributes on a
viewset.
parameters:
- in: path
name: id
@ -2734,13 +2695,13 @@ paths:
content:
application/json:
schema:
$ref: "#/components/schemas/PatchedReadSchedule"
$ref: "#/components/schemas/PatchedSchedule"
application/x-www-form-urlencoded:
schema:
$ref: "#/components/schemas/PatchedReadSchedule"
$ref: "#/components/schemas/PatchedSchedule"
multipart/form-data:
schema:
$ref: "#/components/schemas/PatchedReadSchedule"
$ref: "#/components/schemas/PatchedSchedule"
security:
- cookieAuth: []
- basicAuth: []
@ -2749,16 +2710,10 @@ paths:
content:
application/json:
schema:
$ref: "#/components/schemas/ReadSchedule"
$ref: "#/components/schemas/Schedule"
description: ""
delete:
operationId: schedule_destroy
description: |-
Overrides get_serializer_class to choose the read serializer
for GET requests and the write serializer for POST requests.
Set read_serializer_class and write_serializer_class attributes on a
viewset.
parameters:
- in: path
name: id
@ -5357,7 +5312,7 @@ components:
readOnly: true
import_status:
allOf:
- $ref: "#/components/schemas/FileImportStatusEnum"
- $ref: "#/components/schemas/PlaylistContentKindEnum"
minimum: -2147483648
maximum: 2147483647
filepath:
@ -5609,16 +5564,6 @@ components:
- mime
- name
- size
FileImportStatusEnum:
enum:
- 0
- 1
- 2
type: integer
description: |-
* `0` - Success
* `1` - Pending
* `2` - Failed
ImportedPodcast:
type: object
properties:
@ -5769,7 +5714,7 @@ components:
readOnly: true
import_status:
allOf:
- $ref: "#/components/schemas/FileImportStatusEnum"
- $ref: "#/components/schemas/PlaylistContentKindEnum"
minimum: -2147483648
maximum: 2147483647
filepath:
@ -6345,7 +6290,7 @@ components:
user:
type: integer
nullable: true
PatchedReadSchedule:
PatchedSchedule:
type: object
properties:
id:
@ -6454,16 +6399,6 @@ components:
type: boolean
auto_playlist_repeat:
type: boolean
intro_playlist:
type: integer
nullable: true
override_intro_playlist:
type: boolean
outro_playlist:
type: integer
nullable: true
override_outro_playlist:
type: boolean
PatchedShowDays:
type: object
properties:
@ -6922,9 +6857,9 @@ components:
- 2
type: integer
description: |-
* `0` - File
* `1` - Stream
* `2` - Block
* `0` - Success
* `1` - Pending
* `2` - Failed
PlayoutHistory:
type: object
properties:
@ -7141,7 +7076,41 @@ components:
- id
- key
- user
ReadSchedule:
RecordEnabledEnum:
enum:
- 0
- 1
type: integer
description: |-
* `0` - No
* `1` - Yes
RepeatKindEnum:
enum:
- 0
- 1
- 4
- 5
- 2
type: integer
description: |-
* `0` - Every week
* `1` - Every 2 weeks
* `4` - Every 3 weeks
* `5` - Every 4 weeks
* `2` - Every month
RoleEnum:
enum:
- G
- H
- P
- A
type: string
description: |-
* `G` - Guest
* `H` - Host
* `P` - Manager
* `A` - Admin
Schedule:
type: object
properties:
id:
@ -7203,40 +7172,6 @@ components:
- instance
- position
- starts_at
RecordEnabledEnum:
enum:
- 0
- 1
type: integer
description: |-
* `0` - No
* `1` - Yes
RepeatKindEnum:
enum:
- 0
- 1
- 4
- 5
- 2
type: integer
description: |-
* `0` - Every week
* `1` - Every 2 weeks
* `4` - Every 3 weeks
* `5` - Every 4 weeks
* `2` - Every month
RoleEnum:
enum:
- G
- H
- P
- A
type: string
description: |-
* `G` - Guest
* `H` - Host
* `P` - Manager
* `A` - Admin
ServiceRegister:
type: object
properties:
@ -7296,16 +7231,6 @@ components:
type: boolean
auto_playlist_repeat:
type: boolean
intro_playlist:
type: integer
nullable: true
override_intro_playlist:
type: boolean
outro_playlist:
type: integer
nullable: true
override_outro_playlist:
type: boolean
required:
- auto_playlist_enabled
- auto_playlist_repeat
@ -7314,8 +7239,6 @@ components:
- linked
- live_enabled
- name
- override_intro_playlist
- override_outro_playlist
ShowDays:
type: object
properties:
@ -7602,19 +7525,10 @@ components:
message_offline:
type: string
readOnly: true
replay_gain_enabled:
type: boolean
readOnly: true
replay_gain_offset:
type: number
format: double
readOnly: true
required:
- input_fade_transition
- message_format
- message_offline
- replay_gain_enabled
- replay_gain_offset
StreamState:
type: object
properties:
@ -7840,66 +7754,6 @@ components:
* `4` - Friday
* `5` - Saturday
* `6` - Sunday
WriteSchedule:
type: object
properties:
id:
type: integer
readOnly: true
starts_at:
type: string
format: date-time
ends_at:
type: string
format: date-time
length:
type: string
nullable: true
fade_in:
type: string
format: time
nullable: true
fade_out:
type: string
format: time
nullable: true
cue_in:
type: string
cue_out:
type: string
position:
type: integer
maximum: 2147483647
minimum: -2147483648
position_status:
allOf:
- $ref: "#/components/schemas/PositionStatusEnum"
minimum: -32768
maximum: 32767
broadcasted:
type: integer
maximum: 32767
minimum: -32768
played:
type: boolean
nullable: true
instance:
type: integer
file:
type: integer
nullable: true
stream:
type: integer
nullable: true
required:
- broadcasted
- cue_in
- cue_out
- ends_at
- id
- instance
- position
- starts_at
securitySchemes:
basicAuth:
type: http

View File

@ -1,10 +1,8 @@
from setuptools import find_packages, setup
version = "4.2.0" # x-release-please-version
setup(
name="libretime-api",
version=version,
version="3.2.0",
description="LibreTime API",
author="LibreTime Contributors",
url="https://github.com/libretime/libretime",
@ -26,22 +24,22 @@ setup(
]
},
install_requires=[
"django-cors-headers>=3.14.0,<4.5",
"django-filter>=2.4.0,<24.4",
"django-cors-headers>=3.14.0,<4.4",
"django-filter>=2.4.0,<23.4",
"django>=4.2.0,<4.3",
"djangorestframework>=3.14.0,<3.16",
"drf-spectacular>=0.22.1,<0.29",
"requests>=2.32.2,<2.33",
"djangorestframework>=3.14.0,<3.15",
"drf-spectacular>=0.22.1,<0.27",
"requests>=2.31.0,<2.32",
],
extras_require={
"prod": [
"gunicorn>=22.0.0,<23.1",
"gunicorn>=20.1.0,<21.3",
"psycopg[c]>=3.1.8,<3.2",
"uvicorn[standard]>=0.17.6,<0.33.0",
"uvicorn[standard]>=0.17.6,<0.24.0",
],
"dev": [
"django-coverage-plugin>=3.0.0,<4",
"django-stubs>=5.1.0,<6",
"django-stubs>=1.14.0,<5",
"djangorestframework-stubs>=1.8.0,<4",
"model_bakery>=1.10.1,<2",
"psycopg[binary]>=3.1.8,<4",

View File

@ -1,10 +1,20 @@
#cloud-config
package_update: true
package_upgrade: true
# Maintainer: Zachary Klosko (kloskoz@vcu.edu)
hostname: libretimeTest
timezone: America/New York # change as needed
ntp:
pools: ["north-america.pool.ntp.org"]
servers: ["0.north-america.pool.ntp.org", "0.pool.ntp.org"]
password: hackme
chpasswd: { expire: False }
packages:
- git
apt_update: true
apt_upgrade: true
# Clone repo on init (Change repo url if needed)
# If you need to clone a branch, use git clone --branch branchname repourl
runcmd:
- git clone https://github.com/libretime/libretime /root/libretime
- cd /root/libretime
- HOME=/root bash install "http://$(ip route get 8.8.8.8 | awk '{print $7}'):8080"
- git clone https://github.com/libretime/libretime

View File

@ -2,6 +2,8 @@
# This file is used for development. It it not intended for production!
# See https://libretime.org/docs/developer-manual/development/environment/#docker-compose
#
version: "3.9"
services:
postgres:
ports:

View File

@ -1,3 +1,5 @@
version: "3.9"
services:
postgres:
image: postgres:15
@ -10,13 +12,13 @@ services:
test: pg_isready -U libretime
rabbitmq:
image: rabbitmq:3.13-alpine
image: rabbitmq:3.12-alpine
environment:
RABBITMQ_DEFAULT_VHOST: ${RABBITMQ_DEFAULT_VHOST:-/libretime}
RABBITMQ_DEFAULT_USER: ${RABBITMQ_DEFAULT_USER:-libretime}
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS:-libretime} # Change me !
healthcheck:
test: nc -z 127.0.0.1 5672
test: rabbitmq-diagnostics -q ping
playout:
image: ghcr.io/libretime/libretime-playout:${LIBRETIME_VERSION:-latest}
@ -29,7 +31,7 @@ services:
- ${LIBRETIME_CONFIG_FILEPATH:-./config.yml}:/etc/libretime/config.yml:ro
- libretime_playout:/app
environment:
LIBRETIME_GENERAL_PUBLIC_URL: http://nginx:8080
LIBRETIME_GENERAL_PUBLIC_URL: http://nginx
liquidsoap:
image: ghcr.io/libretime/libretime-playout:${LIBRETIME_VERSION:-latest}
@ -46,7 +48,7 @@ services:
- ${LIBRETIME_CONFIG_FILEPATH:-./config.yml}:/etc/libretime/config.yml:ro
- libretime_playout:/app
environment:
LIBRETIME_GENERAL_PUBLIC_URL: http://nginx:8080
LIBRETIME_GENERAL_PUBLIC_URL: http://nginx
analyzer:
image: ghcr.io/libretime/libretime-analyzer:${LIBRETIME_VERSION:-latest}
@ -59,7 +61,7 @@ services:
- ${LIBRETIME_CONFIG_FILEPATH:-./config.yml}:/etc/libretime/config.yml:ro
- libretime_storage:/srv/libretime
environment:
LIBRETIME_GENERAL_PUBLIC_URL: http://nginx:8080
LIBRETIME_GENERAL_PUBLIC_URL: http://nginx
worker:
image: ghcr.io/libretime/libretime-worker:${LIBRETIME_VERSION:-latest}
@ -71,7 +73,7 @@ services:
volumes:
- ${LIBRETIME_CONFIG_FILEPATH:-./config.yml}:/etc/libretime/config.yml:ro
environment:
LIBRETIME_GENERAL_PUBLIC_URL: http://nginx:8080
LIBRETIME_GENERAL_PUBLIC_URL: http://nginx
api:
image: ghcr.io/libretime/libretime-api:${LIBRETIME_VERSION:-latest}
@ -101,12 +103,11 @@ services:
nginx:
image: nginx
ports:
- 8080:8080
- 8080:80
depends_on:
- legacy
volumes:
- libretime_assets:/var/www/html:ro
- libretime_storage:/srv/libretime:ro
- ${NGINX_CONFIG_FILEPATH:-./nginx.conf}:/etc/nginx/conf.d/default.conf:ro
icecast:

View File

@ -7,8 +7,9 @@ general:
# The internal API authentication key.
# > this field is REQUIRED
api_key:
# The Django API secret key.
# > this field is REQUIRED
# The Django API secret key. If not defined, the value of [general.api_key] will be
# used as fallback.
# > this field will be REQUIRED starting with LibreTime 4.0.0
secret_key:
# List of origins allowed to access resources on the server, the public url
@ -31,8 +32,7 @@ general:
auth: local
storage:
# Path of the storage directory. Make sure to update the Nginx configuration after
# updating the storage path.
# Path of the storage directory.
# > default is /srv/libretime
path: /srv/libretime
@ -325,9 +325,5 @@ stream:
enabled: false
# System output kind.
# > must be one of (alsa, ao, oss, portaudio, pulseaudio)
# > default is pulseaudio
kind: pulseaudio
# System output device.
# > only available for kind=(alsa, pulseaudio)
device:
# > default is alsa
kind: alsa

View File

@ -7,8 +7,9 @@ general:
# The internal API authentication key.
# > this field is REQUIRED
api_key:
# The Django API secret key.
# > this field is REQUIRED
# The Django API secret key. If not defined, the value of [general.api_key] will be
# used as fallback.
# > this field will be REQUIRED starting with LibreTime 4.0.0
secret_key:
# List of origins allowed to access resources on the server, the public url
@ -31,8 +32,7 @@ general:
auth: local
storage:
# Path of the storage directory. Make sure to update the Nginx configuration after
# updating the storage path.
# Path of the storage directory.
# > default is /srv/libretime
path: /srv/libretime
@ -325,9 +325,5 @@ stream:
enabled: false
# System output kind.
# > must be one of (alsa, ao, oss, portaudio, pulseaudio)
# > default is pulseaudio
kind: pulseaudio
# System output device.
# > only available for kind=(alsa, pulseaudio)
device:
# > default is alsa
kind: alsa

View File

@ -7,8 +7,9 @@ general:
# The internal API authentication key.
# > this field is REQUIRED
api_key: some_secret_api_key
# The Django API secret key.
# > this field is REQUIRED
# The Django API secret key. If not defined, the value of [general.api_key] will be
# used as fallback.
# > this field will be REQUIRED starting with LibreTime 4.0.0
secret_key:
# List of origins allowed to access resources on the server, the public url
@ -31,8 +32,7 @@ general:
auth: local
storage:
# Path of the storage directory. Make sure to update the Nginx configuration after
# updating the storage path.
# Path of the storage directory.
# > default is /srv/libretime
path: /srv/libretime
@ -325,9 +325,5 @@ stream:
enabled: false
# System output kind.
# > must be one of (alsa, ao, oss, portaudio, pulseaudio)
# > default is pulseaudio
kind: pulseaudio
# System output device.
# > only available for kind=(alsa, pulseaudio)
device:
# > default is alsa
kind: alsa

View File

@ -1,6 +1,6 @@
server {
listen 8080;
listen [::]:8080;
listen 80;
listen [::]:80;
root /var/www/html/public;
@ -40,11 +40,4 @@ server {
proxy_redirect off;
proxy_pass http://api:9001;
}
# Internal path for serving media files from the API.
location /api/_media {
internal;
# This alias path must match the 'storage.path' configuration field.
alias /srv/libretime;
}
}

View File

@ -42,8 +42,8 @@ general:
# The internal API authentication key.
# > this field is REQUIRED
api_key: "some_random_generated_secret!"
# The Django API secret key.
# > this field is REQUIRED
# The Django API secret key. If not defined, the value of [general.api_key] will be
# used as fallback.
secret_key: "some_random_generated_secret!"
# List of origins allowed to access resources on the server,
@ -72,32 +72,11 @@ The `storage` section configure the project storage.
```yml
storage:
# Path of the storage directory. Make sure to update the Nginx configuration after
# updating the storage path.
# Path of the storage directory.
# > default is /srv/libretime
path: "/srv/libretime"
```
:::caution
After editing the `storage.path` field, make sure to update the LibreTime Nginx configuration file with the new value.
In the example below, we are changing the path from `/srv/libretime` to `/mnt/data`:
```patch
...
# Internal path for serving media files from the API.
location /api/_media {
internal;
# This alias path must match the 'storage.path' configuration field.
- alias /srv/libretime;
+ alias /mnt/data;
}
```
:::
## Database
The `database` section configure the PostgreSQL connection.
@ -547,15 +526,11 @@ stream:
system:
- # Whether the output is enabled.
# > default is false
enabled: true
enabled: false
# System output kind.
# > must be one of (alsa, ao, oss, portaudio, pulseaudio)
# > default is pulseaudio
kind: "pulseaudio"
# System output device.
# > only available for kind=(alsa, pulseaudio)
device: "alsa_output.pci-0000_00_1f.3-platform-skl_hda_dsp_generic.HiFi__hw_sofhdadsp__sink"
# > default is alsa
kind: "alsa"
```
## LDAP

View File

@ -50,7 +50,7 @@ check them against pam.
The above configuration expects a PAM configuration for the `http-libretime` service.
To configure this you need to create the file `/etc/pam.d/http-libretime` with the following contents.
To confiure this you need to create the file `/etc/pam.d/http-libretime` with the following contents.
```
auth required pam_sss.so
@ -113,68 +113,3 @@ general:
```
You should now be able to use your FreeIPA credentials to log in to LibreTime.
## Setup Header Authentication
If you have an SSO system that supports trusted SSO header authentication such as [Authelia](https://www.authelia.com/),
you can configure LibreTime to login users based on those trusted headers.
This allows users to only need to log in once on the SSO system and not need to log in again. It also allows LibreTime
to indirectly support other authentication mechanisms such as OAuth2.
This ONLY affects Legacy/Legacy API auth and does NOT affect API V2 auth.
### Configure Headers
LibreTime needs to know what headers are sent, and what information is available to it. You can also
setup a predefined group mapping so users are automatically granted the desired permissions.
This configuration is in `/etc/libretime/config.yml`. The following is an example configuration for an SSO service
that does the following:
- Sends the username in the `Remote-User` HTTP header.
- Sends the email in the `Remote-Email` HTTP header.
- Sends the name in the `Remote-Name` HTTP header. Example `John Doe`
- Sends the comma delimited groups in the `Remote-Groups` HTTP header. Example `group 1,lt-admin,group2`
- Has an IP of `10.0.0.34` (not required). When not provided it is not checked.
- Users with the `lt-host` group should get host privileges.
- Users with the `lt-admin` group should get admin privileges.
- Users with the `lt-pm` group should get program manager privileges.
- Users with the `lt-superadmin` group should get super admin privileges.
- All other users should get guest privileges.
```yml
header_auth:
user_header: Remote-User # This is the default and could be omitted
groups_header: Remote-Groups # This is the default and could be omitted
email_header: Remote-Email # This is the default and could be omitted
name_header: Remote-Name # This is the default and could be omitted
proxy_ip: 10.0.0.34
group_map:
host: lt-host
program_manager: lt-pm
admin: lt-admin
superadmin: lt-superadmin
```
If the `user_header` is not found in the request, users will be kicked to the login page
with a message that their username/password is invalid and will not be able to log in. When `proxy_ip` is provided
it will check that the request is coming from the correct proxy before doing the login. This prevents users who have
internal network access from being able to login as whoever they want in LibreTime.
::: warning
If `proxy_ip` is not provided any user on the internal network can log in as any user in LibreTime.
:::
### Enable Header authentication
After everything is set up properly you can enable header auth in `config.yml`:
```yml
general:
auth: LibreTime_Auth_Adaptor_Header
```
You should now be automatically logged into LibreTime when you click the `Login` button.

View File

@ -22,7 +22,7 @@ First, set the version you want to install:
echo LIBRETIME_VERSION="{vars.version}" > .env
</CodeBlock>
Download the docker compose files from the repository:
Download the docker-compose files from the repository:
```bash
# Load LIBRETIME_VERSION variable
@ -106,16 +106,16 @@ You can find more details in the `docker-compose.yml` file or on the external se
Next, run the following commands to setup the database:
```bash
docker compose run --rm api libretime-api migrate
docker-compose run --rm api libretime-api migrate
```
Finally, start the services, and check that they're running using the following commands:
```bash
docker compose up -d
docker-compose up -d
docker compose ps
docker compose logs -f
docker-compose ps
docker-compose logs -f
```
## Securing LibreTime

View File

@ -117,12 +117,12 @@ git checkout {vars.version}
## Run the installer
By default the installer will configure LibreTime to listen at the port `8080`. We recommend that you configure a [reverse proxy in front of LibreTime](./reverse-proxy.md) to secure the connection using HTTPS, and route the traffic from the ports `80`/`443` to the LibreTime server.
By default the installer will configure LibreTime to listen at the port `80`, but this isn't the recommended way to install LibreTime. Instead you should configure a [reverse proxy in front of LibreTime](./reverse-proxy.md) to secure the connection using HTTPS, and route the traffic to the LibreTime server.
Install LibreTime with the following command, be sure to replace `https://libretime.example.org` with the public url of your installation:
```bash
sudo ./install https://libretime.example.org
sudo ./install --listen-port 8080 https://libretime.example.org
```
:::caution
@ -131,17 +131,23 @@ When upgrading be sure to run the installer using the same arguments you used du
:::
:::warning
To update the LibreTime nginx configuration file, for example to change the `--listen-port`, make sure to add the `--update-nginx` flag to allow overwriting the existing configuration file.
:::
If you need to change some configuration, the install script can be configured using flags or environment variables. Changing the listening port of LibreTime or whether you want to install some dependency by yourself, you could run the following:
```bash
# Install LibreTime on your system with the following tweaks:
# - don't install the liquidsoap package (remember to install liquidsoap yourself)
# - set the listen port to 8081
# - set the listen port to 8080
# - don't run the PostgreSQL setup (remember to setup PostgreSQL yourself)
sudo \
LIBRETIME_PACKAGES_EXCLUDES='liquidsoap' \
./install \
--listen-port 8081 \
--listen-port 8080 \
--no-setup-postgresql \
https://libretime.example.org
```
@ -150,7 +156,7 @@ You can persist the install configuration in a `.env` file next to the install s
```
LIBRETIME_PACKAGES_EXCLUDES='liquidsoap'
LIBRETIME_LISTEN_PORT='8081'
LIBRETIME_LISTEN_PORT='8080'
LIBRETIME_SETUP_POSTGRESQL=false
LIBRETIME_PUBLIC_URL='https://libretime.example.org'
```
@ -230,9 +236,6 @@ server {
server_name libretime.example.org;
client_max_body_size 512M;
client_body_timeout 300s;
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;

View File

@ -36,6 +36,12 @@ flowchart LR
:::warning
By default, the installer configures nginx to listen on port 80, so you need to run the installer with the `--listen-port 8080` option to set the desired port.
:::
:::warning
The current input and output streams are Icecast based protocols and doesn't support being behind a reverse proxy. **Don't attempt** to [reverse proxy Icecast](#icecast) or the Liquidsoap harbor inputs.
You can [secure the Icecast output streams](#securing-the-icecast-output-streams) by adding an additional Icecast socket and reusing the TLS certificates used to secure LibreTime.
@ -52,6 +58,8 @@ In this documentation, we will use `libretime.example.org` as domain name pointi
:::
If LibreTime is running on the same host as the reverse proxy, you need to change the LibreTime web server default listening port because the reverse proxy needs to listen on the `80`and `443` ports.
Be sure that your firewall and network allows communications from the reverse proxy to the services. You can use `ping`, `telnet` and `curl` to check that communication is working.
## Install a reverse proxy
@ -76,9 +84,6 @@ server {
listen 80;
server_name libretime.example.org;
client_max_body_size 512M;
client_body_timeout 300s;
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
@ -127,9 +132,6 @@ server {
listen 80;
server_name libretime.example.org;
client_max_body_size 512M;
client_body_timeout 300s;
if ($host = libretime.example.org) {
return 301 https://$host$request_uri;
} # managed by Certbot
@ -141,9 +143,6 @@ server {
listen 443 ssl; # managed by Certbot
server_name libretime.example.org;
client_max_body_size 512M;
client_body_timeout 300s;
ssl_certificate /etc/letsencrypt/live/libretime.example.org/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/libretime.example.org/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot

View File

@ -17,12 +17,6 @@ Setting a higher bitrate for your output stream will only benefit your listeners
:::
:::caution
The liquidsoap playout handler version 1.4.3 shipped in Debian Bullseye and 1.4.1 shipped in Ubuntu Focal doesn't support AAC streaming output. If you want to stream AAC, you will need to replace the liquidsoap package with a version that supports AAC. See this [tutorial](./tutorials/setup-liquidsoap-aac-streaming.md) for more information.
:::
## Icecast
### UTF-8 metadata in Icecast MP3 streams

View File

@ -1,63 +0,0 @@
---
title: How to update liquidsoap to support AAC streaming
---
This tutorials walks you though the steps required to replace the liquidsoap package with a version that supports AAC streaming.
:::warning
Replacing the liquidsoap package has security implications, since this will remove the package from the system's package manager. This means that the package manager will not be able to update the liquidsoap package in the future. This includes backports of security fixes.
Libretime is NOT compatible with Liquidsoap 2.x at the time of this writing. Future versions of Libretime will support Liquidsoap 2.x which will render these instructions obsolete.
:::
:::info
Lets assume you already [installed LibreTime using the native OS installer](../install/install-using-the-installer.md). Execute the following commands as the libretime user.
:::
## 1. Obtain liquidsoap with AAC support
For Ubuntu 20.04 LTS ('focal'), use the following file:
```bash
wget https://github.com/savonet/liquidsoap/releases/download/v1.4.4/liquidsoap-v1.4.4_1.4.4-ubuntu-focal-amd64-1_amd64.deb
```
For Debian 11 ('Bullseye'), first enable non-free package source for libfdk-aac support:
```bash
sudo apt install software-properties-common
sudo apt-add-repository -c non-free
```
Then use the following file:
```bash
wget https://github.com/savonet/liquidsoap/releases/download/v1.4.4/liquidsoap-v1.4.4_1.4.4-debian-testing-amd64-1_amd64.deb
```
## 2. Install and replace the liquidsoap package
Install the package using `apt`, then remove the old liquidsoap dependencies:
```bash
sudo apt -y install ./liquidsoap-v1.4.4_1.4.4-*-amd64-1_amd64.deb
sudo apt -y autoremove
```
## 3. Configure LibreTime to use the new liquidsoap package
Nothing to do, this is a drop-in replacement. Just restart the libretime target once and then check the status page in the LibreTime web interface to see if the liquidsoap service is running.
```bash
sudo systemctl restart libretime.target
```
:::warning
If you want to update LibreTime in the future, you'll need to re-run the installer schript. This will replace the liquidsoap package with the version that doesn't support AAC streaming. Add `--packages-excludes liquidsoap` to the installer command to prevent this from happening.
:::

View File

@ -54,7 +54,7 @@ liable to legal action.
If you want to go down the commercial music route, check out the
https://www.prsformusic.com and https://www.ppluk.com websites for UK licence
details. In the USA, the https://www.soundexchange.com website currently quotes
a 1000 (per Jan. 2024) dollar minimum annual fee for non-commercial webcasters, plus a usage fee
a 500 dollar minimum annual fee for non-commercial webcasters, plus a usage fee
above a certain number of listener hours, for the right to stream music
recordings to listeners. See the websites of [ASCAP](https://www.ascap.com),
[BMI](https://www.bmi.com) and [SESAC](https://www.sesac.com) for details of music

View File

@ -13,8 +13,8 @@ LibreTime development workflows follow the standardized [C4 development process]
- [2.4. Development Process](https://rfc.zeromq.org/spec/42/#24-development-process)
- `16.` Maintainers MAY NOT merge incorrect patches.
- [2.5. Branches and Releases](https://rfc.zeromq.org/spec/42/#25-branches-and-releases)
- `1.` The project SHALL have a development branch (`main`) that always holds the latest in-progress version and SHOULD always build. The project MAY have a bug fixes only branch (`stable-*`) that always holds the current stable version and SHOULD always build.
- `3.` To make a stable release a Maintainer shall tag the repository. Stable releases SHALL always be released from the repository `main` or `stable-*` branches.
- `1.` The project SHALL have a development branch (`main`) that always holds the latest in-progress version and SHOULD always build. The project MAY have a bug fixes only branch (`stable`) that always holds the current stable version and SHOULD always build.
- `3.` To make a stable release a Maintainer shall tag the repository. Stable releases SHALL always be released from the repository `main` or `stable` branches.
## Contribute financially

View File

@ -14,16 +14,16 @@ To setup a docker-compose development environment, run the following commands:
# Clean and build
make clean
cp .env.dev .env
DOCKER_BUILDKIT=1 docker compose build
DOCKER_BUILDKIT=1 docker-compose build
# Setup
make dev-certs
docker compose run --rm legacy make build
docker compose run --rm api libretime-api migrate
docker-compose run --rm legacy make build
docker-compose run --rm api libretime-api migrate
# Run
docker compose up -d
docker compose logs -f
docker-compose up -d
docker-compose logs -f
```
:::info
@ -33,7 +33,7 @@ You may also use the following `make clean dev` shortcut:
```bash
make clean dev
docker compose logs -f
docker-compose logs -f
```
:::

View File

@ -7,7 +7,7 @@ title: Development workflows
LibreTime uses [Github pull requests to manage changes](https://docs.github.com/en/get-started/quickstart/contributing-to-projects). The workflow looks like this:
- [Create a fork of the project](https://docs.github.com/en/get-started/quickstart/fork-a-repo).
- Check out the `main` branch.
- Check out the `main` branch. If you're making a minor or small documentation change you can check out the `stable` branch.
- Create a new branch based on the checked out branch.
- Work on your changes locally. Try to keep each commit small to make reviews easier.
- Lint and test the codebase, for example using the `make lint` or `make test` commands inside the app folder you want to check.

View File

@ -6,17 +6,47 @@ title: Releases
This guide walks you through the steps required to release a new version of LibreTime.
### 1. Inspect the release pull request
:::caution
A release pull request is maintained by [`release-please`](https://github.com/googleapis/release-please). `release-please` guesses the next version to release based on the commit history, and will generate a changelog for that release.
This guide is still a work in progress, and doesn't cover every use cases. Depending on
the version bump, some steps might be wrong. For example, in case of a patch release,
the documentation requires different changes.
Once a release is desired, checkout the release branch:
:::
Before releasing a new version, make sure linter don't fail and tests are passing.
Start by cleaning the repository and make sure you don't have uncommitted changes:
```
git checkout main
make clean
git status
```
Choose the next version based the our [versioning schema](#versioning-schema):
```bash
# For a release on the main branch
git checkout release-please--branches--main--components--libretime
# For a release on the stable-4.x branch
git checkout release-please--branches--stable-4.x--components--libretime
export VERSION=3.0.0-beta.0
```
Create a new `release-$VERSION` branch and release commit to prepare a release pull request:
```bash
git checkout -b "release-$VERSION"
export COMMIT_MESSAGE="chore: release $VERSION"
git commit --allow-empty --message="$COMMIT_MESSAGE"
```
### 1. Version bump
Write the new `$VERSION` to the VERSION file, and bump the python packages version:
```bash
bash tools/bump-python-version.sh "$VERSION"
git add .
git commit --fixup ":/$COMMIT_MESSAGE"
```
### 2. Release note
@ -42,16 +72,48 @@ Reset and clean the `docs/releases/unreleased.md` file for a future version.
Commit the release note changes:
```bash
git add docs/releases
git commit -m "docs: add release note"
git add .
git commit --fixup ":/$COMMIT_MESSAGE"
```
### 4. Merge the release pull request
### 3. Create a new pull request
Push any changes that we previously made to the release branch:
Squash the changes and open a pull request for others to review:
```bash
git push
git rebase --autosquash --interactive main
```
Once the pull request CI succeeded and everything is ready, merge the release pull request. `release-please` will create a tag and a release, which will trigger the final release pipeline that will upload the tarball as release assets.
Merge the pull request when it's reviewed and ready.
### 4. Create and push a tag
Pull the merged release commit:
```bash
git checkout main
git pull upstream main
```
Make sure `HEAD` is the previously merged release commit and tag it with the new version:
```bash
git show --quiet
git tag -a -m "$VERSION" "$VERSION"
```
Generate the changelog for the newly tagged version:
```bash
make changelog
git add .
git commit -m "chore: generate changelog for $VERSION"
```
Push the tag upstream to finalize the release process:
```bash
git push upstream main --follow-tags
```

View File

@ -1,5 +1,5 @@
---
title: LibreTime 3.2.0
title: Unreleased
---
import ReleaseHead from './\_release-head.md';

View File

@ -1,65 +0,0 @@
---
title: LibreTime 4.0.0
---
import ReleaseHead from './\_release-head.md';
<ReleaseHead date='2024-01-07' version='4.0.0'/>
## :sparkling_heart: Contributors
The LibreTime project wants to thank the following contributors for authoring PRs to this release:
- @jooola
- @maxtimbo
- @mp3butcher
## :rocket: Features
- default system output is now `pulseaudio` ([#2842](https://github.com/libretime/libretime/issues/2842)) ([083ee3f](https://github.com/libretime/libretime/commit/083ee3f1dd74441e288b4d63178ae9cea12ba286)), closes [#2542](https://github.com/libretime/libretime/issues/2542)
- disable uvicorn worker lifespan ([#2845](https://github.com/libretime/libretime/issues/2845)) ([8743c84](https://github.com/libretime/libretime/commit/8743c84d0f007a5430e9059c197a261e613cc642))
- **installer:** add the `--storage-path` flag ([#2865](https://github.com/libretime/libretime/issues/2865)) ([5b23852](https://github.com/libretime/libretime/commit/5b23852f8d144f0c7cdeb62831f7b1a27872b40e))
- **installer:** change default listen port to 8080 ([#2852](https://github.com/libretime/libretime/issues/2852)) ([f72b7f9](https://github.com/libretime/libretime/commit/f72b7f9c9727800a9d77d64c540c12f272bb0ae3))
- **installer:** remove the `--update-nginx` flag ([#2851](https://github.com/libretime/libretime/issues/2851)) ([35d7eac](https://github.com/libretime/libretime/commit/35d7eace13c2b9667fdb41fec0788118e0c5e63f))
- **playout:** configure device for alsa and pulseaudio system outputs ([#2654](https://github.com/libretime/libretime/issues/2654)) ([06af18b](https://github.com/libretime/libretime/commit/06af18b84e7dfaad95e3b55dda22ec1ddad27050))
- rewrite cloud-init config ([#2853](https://github.com/libretime/libretime/issues/2853)) ([8406d52](https://github.com/libretime/libretime/commit/8406d520d7a7bea4060be8a00e360bcf413cb2d5))
- run python in optimized mode ([#2874](https://github.com/libretime/libretime/issues/2874)) ([3f7fc99](https://github.com/libretime/libretime/commit/3f7fc99b6b343fbc8df319d8130ba8247aea96d8))
- the `general.secret_key` configuration field is now required ([#2841](https://github.com/libretime/libretime/issues/2841)) ([0d2d1a2](https://github.com/libretime/libretime/commit/0d2d1a26731a2b41ce5e574ed6de9950eaae4153)), closes [#2426](https://github.com/libretime/libretime/issues/2426)
- use nginx to serve media files ([#2860](https://github.com/libretime/libretime/issues/2860)) ([4603c17](https://github.com/libretime/libretime/commit/4603c1759f29b8a1adb3e83d610ca00e778d76bd))
## :bug: Bug fixes
- add parent function name in setValue exception ([#2777](https://github.com/libretime/libretime/issues/2777)) ([c764a5a](https://github.com/libretime/libretime/commit/c764a5a648ac6cf6c1f63cd9be6de9fe07c40988))
- **api:** ensure non ascii paths are handled by X-Accel-Redirect ([#2861](https://github.com/libretime/libretime/issues/2861)) ([0ce63f3](https://github.com/libretime/libretime/commit/0ce63f3bf0448580170024cdde96ee351ee5c358))
- **api:** enum schema description ([#2803](https://github.com/libretime/libretime/issues/2803)) ([976b70e](https://github.com/libretime/libretime/commit/976b70ed32a0e774cc0b72b8332372be32799ed1))
- **api:** let nginx handle the media file content type ([#2862](https://github.com/libretime/libretime/issues/2862)) ([72268ad](https://github.com/libretime/libretime/commit/72268ad9bb1a96b24efda7143b9371d6fd98ca03))
- **api:** move gunicorn worker config to python file ([#2854](https://github.com/libretime/libretime/issues/2854)) ([43221d9](https://github.com/libretime/libretime/commit/43221d9d7f34ba98a14db9906e350cb494a86b25))
- **api:** paths with question marks chars are handled by X-Accel-Redirect ([#2875](https://github.com/libretime/libretime/issues/2875)) ([b2c1ceb](https://github.com/libretime/libretime/commit/b2c1ceb89fafc76f18ec650d19ec0ff03e4a20b0))
- **deps:** update dependency friendsofphp/php-cs-fixer to &lt;3.42.1 (main) ([#2765](https://github.com/libretime/libretime/issues/2765)) ([8ae4dce](https://github.com/libretime/libretime/commit/8ae4dce9e7c013c1f66f1b4d5da4a8c91d3419b7))
- **deps:** update dependency friendsofphp/php-cs-fixer to &lt;3.43.2 (main) ([#2848](https://github.com/libretime/libretime/issues/2848)) ([62e5f4d](https://github.com/libretime/libretime/commit/62e5f4dfbb76ab1919c4905570cc34274c685cef))
- **deps:** update dependency friendsofphp/php-cs-fixer to &lt;3.45.1 (main) ([#2855](https://github.com/libretime/libretime/issues/2855)) ([6f84328](https://github.com/libretime/libretime/commit/6f8432838058be6ef1cfa7858f17b8272929896e))
- **deps:** update dependency friendsofphp/php-cs-fixer to &lt;3.46.1 (main) ([#2868](https://github.com/libretime/libretime/issues/2868)) ([4827dbc](https://github.com/libretime/libretime/commit/4827dbce711262e90238bb3b6c0a35b1ce3d6877))
- **legacy:** allow uploading opus files ([#2804](https://github.com/libretime/libretime/issues/2804)) ([f252a16](https://github.com/libretime/libretime/commit/f252a16637e113ceb1dd340fb7aad31af9c23ff0))
- **legacy:** declare previously undeclared variable ([#2793](https://github.com/libretime/libretime/issues/2793)) ([e2cfbf4](https://github.com/libretime/libretime/commit/e2cfbf4c038f28874a206df5805f04f69a40647b))
- **legacy:** ensure last played criteria works with never played files ([#2840](https://github.com/libretime/libretime/issues/2840)) ([24ee383](https://github.com/libretime/libretime/commit/24ee3830c23f7147f82febe3d3c6743d5ae8d4e6))
- **playout:** increase file download chunk size to 8192 bytes ([#2863](https://github.com/libretime/libretime/issues/2863)) ([7ed1be1](https://github.com/libretime/libretime/commit/7ed1be1816abef20b9ae59a8c66a9e48a34f37c5))
- **playout:** remove empty file when the download request failed ([#2864](https://github.com/libretime/libretime/issues/2864)) ([2facbfa](https://github.com/libretime/libretime/commit/2facbfaff23d4df0e7531b82f04f932bb2c4c9a4))
- **worker:** unbound variable when episode url returns HTTP 404 ([#2844](https://github.com/libretime/libretime/issues/2844)) ([3f39689](https://github.com/libretime/libretime/commit/3f396895e588e62183e01d17927d9bdbea512ee0))
## :arrow_up: Upgrading
### Installer changes
The installer received a few breaking changes:
- The default listen port for the installer is now `8080`. We recommend that you put a reverse proxy in front of LibreTime with your customizations.
- The `--update-nginx` flag was removed from the installer. The nginx configuration deployed by the installer will now always be overwritten. Again, we recommend that you put a reverse proxy in front of LibreTime with your customizations.
- The media file serving is now handled by Nginx instead of the API service. The configuration `storage.path` field is now used in the Nginx configuration, so make sure that the Nginx configuration file has the right storage path. To update the Nginx configuration file with the installer, you can use the `--storage-path` option to specify your custom storage path.
### The `general.secret_key` configuration field is required
The `general.secret_key` field in the [configuration file](../admin-manual/configuration.md#general) is now **required**, to prevent reusing the `general.api_key` for cryptographic usage.
### The `stream.outputs.system[].kind` configuration field now defaults to `pulseaudio`
The `stream.outputs.system[].kind` field in the [configuration file](../admin-manual/configuration.md#general) default value changed from `alsa` to `pulseaudio`. Make sure to update your configuration file if you rely on the default system output.

View File

@ -1,31 +0,0 @@
---
title: LibreTime 4.1.0
---
import ReleaseHead from './\_release-head.md';
<ReleaseHead date='2024-05-05' version='4.1.0'/>
## :sparkling_heart: Contributors
The LibreTime project wants to thank the following contributors for authoring PRs to this release:
- @caveman99
- @jooola
- @kmahelona
- @mp3butcher
- @paddatrapper
## :rocket: Features
Please see the [changelog](https://github.com/libretime/libretime/blob/main/CHANGELOG.md#410-2024-05-05).
## :bug: Bug fixes
Please see the [changelog](https://github.com/libretime/libretime/blob/main/CHANGELOG.md#410-2024-05-05).
## :arrow_up: Upgrading
### Replay gain modifier preference
The `replay_gain_modifier` preference is now stored as system preference. Please check and save the replay gain modifier preference manually to make sure the preference is up to date and usable.

View File

@ -1,25 +0,0 @@
---
title: LibreTime 4.2.0
---
import ReleaseHead from './\_release-head.md';
<ReleaseHead date='2024-06-22' version='4.2.0'/>
## :sparkling_heart: Contributors
The LibreTime project wants to thank the following contributors for authoring PRs to this release:
- @conet
- @dakriy
- @jooola
- @paddatrapper
- @rjhelms
## :rocket: Features
Please see the [changelog](https://github.com/libretime/libretime/blob/main/CHANGELOG.md#420-2024-06-22).
## :bug: Bug fixes
Please see the [changelog](https://github.com/libretime/libretime/blob/main/CHANGELOG.md#420-2024-06-22).

View File

@ -30,4 +30,3 @@ New releases target the current stable distributions release, and development sh
| Versions | | | | |
| 3.0.x | deprecated | deprecated | recommended | recommended |
| 3.1.x | | | recommended | recommended |
| 4.0.x | | | recommended | recommended |

View File

@ -12,12 +12,8 @@ The LibreTime project wants to thank the following contributors for authoring PR
## :rocket: Features
Please see the [changelog](https://github.com/libretime/libretime/blob/main/CHANGELOG.md).
## :bug: Bug fixes
Please see the [changelog](https://github.com/libretime/libretime/blob/main/CHANGELOG.md).
## :fire: Deprecation and removal
## :arrow_up: Before upgrading

View File

@ -37,7 +37,6 @@ Smart blocks are automatically filled with media files from the LibreTime librar
To create a smart block, click the **Smartblocks** button on the left sidebar, and select **New** from the toolbar. Like a playlist, smart blocks can have a title and description, which you can edit. This helps you find relevant smart blocks in searches.
Fill out the smart block's **Name**, **Search Criteria**, and **Limit to** sections. The search criteria can be any one of LibreTime's metadata categories, such as **Title**, **Creator** or **Genre**. The modifier depends on whether the metadata in question contains letters or numbers. For example, **Title** has modifiers including _contains_ and _starts with_, whereas the modifiers for **BPM** include _is greater than_ and _is in the range_.
To filter tracks using today's date information, use the `now{}` macro. Format characters are listed in the [php documentation](https://www.php.net/manual/en/datetime.format.php). For example, to filter to tracks with a **Title** that ends in `Instrumental Jan 2024` where `Jan 2024` is the current month and year, add a criteria for **Title** with a modifier of **ends with** and a value of `Instrumental now{M Y}`.
If you have a large number of files which meet the criteria that you specify, you may wish to limit the duration of the smart block using the **Limit to** field, so that it fits within the show you have in mind. Select **hours**, **minutes** or **items** from the drop-down menu, and click the **Generate** button again, if it's a static smart block. Then click the **Save** button.

View File

@ -79,8 +79,6 @@ indicator.
| Add Autoloading Playlist? | If checked, allows for the following options |
| Select Playlist | Select the playlist the show will autofill from (shows autofill exactly one hour before air). If you wish to use a smartblock you must add it to a playlist and then select that playlist. This can be used to auto schedule new podcast episodes to air. |
| Repeat Playlist Until Show Is Full | If checked, the playlist will be added to the show multiple times until the slot is full. Useful for applying a one-hour music playlist made up of smartblocks to a two-hour show. |
| Select Intro Playlist | Select the playlist to replace the global intro playlist from settings. If you wish to use a smartblock you must add it to a playlist and then select that playlist. |
| Select Outro Playlist | Select the playlist to replace the global outro playlist from settings. If you wish to use a smartblock you must add it to a playlist and then select that playlist. |
| _Live Stream Input_ | |
| Use LibreTime/Custom Authentication | |
| Show Source | |

Some files were not shown because too many files have changed in this diff Show More