Compare commits
No commits in common. "main" and "3.0.0-beta.2" have entirely different histories.
main
...
3.0.0-beta
|
@ -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 -}}
|
|
@ -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
|
|
@ -4,8 +4,6 @@ HDA
|
||||||
ro
|
ro
|
||||||
|
|
||||||
# Names
|
# Names
|
||||||
conet
|
|
||||||
falso
|
|
||||||
flor
|
flor
|
||||||
|
|
||||||
# TODO: See https://github.com/savonet/liquidsoap/issues/1654
|
# TODO: See https://github.com/savonet/liquidsoap/issues/1654
|
||||||
|
|
2
.env.dev
2
.env.dev
|
@ -1,3 +1,3 @@
|
||||||
LIBRETIME_VERSION=main
|
LIBRETIME_VERSION=main
|
||||||
LIBRETIME_CONFIG_FILEPATH=./dev/config.yml
|
LIBRETIME_CONFIG_FILEPATH=./docker/config.dev.yml
|
||||||
NGINX_CONFIG_FILEPATH=./docker/nginx.conf
|
NGINX_CONFIG_FILEPATH=./docker/nginx.conf
|
||||||
|
|
|
@ -5,6 +5,6 @@ contact_links:
|
||||||
url: https://discourse.libretime.org/
|
url: https://discourse.libretime.org/
|
||||||
about: Please find existing questions and discussions here.
|
about: Please find existing questions and discussions here.
|
||||||
|
|
||||||
- name: LibreTime Chat (#libretime:matrix.org)
|
- name: LibreTime Chat
|
||||||
url: https://matrix.to/#/#libretime:matrix.org
|
url: https://chat.libretime.org/
|
||||||
about: Discuss with the LibreTime community.
|
about: Discuss with the LibreTime community.
|
||||||
|
|
|
@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
{".":"4.2.0"}
|
|
|
@ -6,11 +6,8 @@
|
||||||
"commitMessageAction": "lock file maintenance",
|
"commitMessageAction": "lock file maintenance",
|
||||||
"commitMessageExtra": "({{packageFile}})",
|
"commitMessageExtra": "({{packageFile}})",
|
||||||
"branchTopic": "lock-file-maintenance-{{packageFile}}",
|
"branchTopic": "lock-file-maintenance-{{packageFile}}",
|
||||||
"schedule": ["after 4am and before 5am on monday"],
|
"schedule": ["after 4am and before 5am on monday"]
|
||||||
"automerge": true,
|
|
||||||
"automergeType": "branch"
|
|
||||||
},
|
},
|
||||||
"baseBranches": ["main"],
|
|
||||||
"labels": ["dependencies"],
|
"labels": ["dependencies"],
|
||||||
"packageRules": [
|
"packageRules": [
|
||||||
{
|
{
|
||||||
|
@ -27,9 +24,17 @@
|
||||||
"rangeStrategy": "widen"
|
"rangeStrategy": "widen"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"matchManagers": ["github-actions", "pre-commit"],
|
"matchPaths": ["website/**"],
|
||||||
"automerge": true,
|
"addLabels": ["javascript"]
|
||||||
"automergeType": "branch"
|
},
|
||||||
|
{
|
||||||
|
"matchUpdateTypes": ["patch"],
|
||||||
|
"matchPaths": [
|
||||||
|
".github/workflows/*",
|
||||||
|
".pre-commit-config.yaml",
|
||||||
|
"website/**"
|
||||||
|
],
|
||||||
|
"automerge": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
# Configuration for probot-stale - https://github.com/probot/stale
|
||||||
|
|
||||||
|
# Number of days of inactivity before an Issue or Pull Request becomes stale (5 months)
|
||||||
|
daysUntilStale: 150
|
||||||
|
|
||||||
|
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed. (1 month)
|
||||||
|
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
|
||||||
|
daysUntilClose: 30
|
||||||
|
|
||||||
|
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
|
||||||
|
exemptLabels:
|
||||||
|
- "status: pinned"
|
||||||
|
- "status: maybe later"
|
||||||
|
- "security"
|
||||||
|
|
||||||
|
# Set to true to ignore issues in a project (defaults to false)
|
||||||
|
exemptProjects: false
|
||||||
|
|
||||||
|
# Set to true to ignore issues in a milestone (defaults to false)
|
||||||
|
exemptMilestones: true
|
||||||
|
|
||||||
|
# Set to true to ignore issues with an assignee (defaults to false)
|
||||||
|
exemptAssignees: true
|
||||||
|
|
||||||
|
# Label to use when marking as stale
|
||||||
|
staleLabel: "status: stalled"
|
||||||
|
|
||||||
|
# Comment to post when marking as stale. Set to `false` to disable
|
||||||
|
markComment: >
|
||||||
|
This issue has been automatically marked as stale because it has not had
|
||||||
|
activity in the last 5 months. It will be closed if no activity occurs in
|
||||||
|
the next month.
|
||||||
|
|
||||||
|
Please chat to us on [discourse](https://discourse.libretime.org/) or
|
||||||
|
ask for help on our [chat](https://chat.libretime.org/) if you have any
|
||||||
|
questions or need further support with getting this issue resolved.
|
||||||
|
|
||||||
|
You may also label an issue as *pinned* if you would like to make sure
|
||||||
|
that it does not get closed by this bot.
|
||||||
|
|
||||||
|
# Comment to post when removing the stale label.
|
||||||
|
# unmarkComment: >
|
||||||
|
# Your comment here.
|
||||||
|
|
||||||
|
# Comment to post when closing a stale Issue or Pull Request.
|
||||||
|
closeComment: >
|
||||||
|
This issue has been automatically closed after is was marked as stale and
|
||||||
|
did not receive any further inputs.
|
||||||
|
|
||||||
|
Feel free to let us know on [discourse](https://discourse.libretime.org/) or
|
||||||
|
ask for help on our [chat](https://chat.libretime.org/) if you feel this
|
||||||
|
issue should not have been closed.
|
||||||
|
|
||||||
|
Thank you for your contributions.
|
||||||
|
|
||||||
|
# Limit the number of actions per hour, from 1-30. Default is 30
|
||||||
|
limitPerRun: 30
|
|
@ -25,11 +25,11 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: "3.x"
|
python-version: "3.x"
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/pip
|
path: ~/.cache/pip
|
||||||
key: ${{ runner.os }}-pip-${{ inputs.context }}-${{ hashFiles(format('{0}/{1}', inputs.context, '**/setup.py')) }}
|
key: ${{ runner.os }}-pip-${{ inputs.context }}-${{ hashFiles(format('{0}/{1}', inputs.context, '**/setup.py')) }}
|
||||||
|
@ -51,8 +51,10 @@ jobs:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
release:
|
release:
|
||||||
- focal
|
- buster
|
||||||
- bullseye
|
- bullseye
|
||||||
|
- bionic
|
||||||
|
- focal
|
||||||
- jammy
|
- jammy
|
||||||
|
|
||||||
container:
|
container:
|
||||||
|
@ -63,9 +65,9 @@ jobs:
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/pip
|
path: ~/.cache/pip
|
||||||
key: ${{ matrix.release }}-pip-${{ inputs.context }}-${{ hashFiles(format('{0}/{1}', inputs.context, '**/setup.py')) }}
|
key: ${{ matrix.release }}-pip-${{ inputs.context }}-${{ hashFiles(format('{0}/{1}', inputs.context, '**/setup.py')) }}
|
||||||
|
@ -73,11 +75,11 @@ jobs:
|
||||||
${{ matrix.release }}-pip-${{ inputs.context }}
|
${{ matrix.release }}-pip-${{ inputs.context }}
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: make test-coverage
|
run: make test
|
||||||
working-directory: ${{ inputs.context }}
|
working-directory: ${{ inputs.context }}
|
||||||
|
|
||||||
- name: Report coverage
|
- name: Report coverage
|
||||||
uses: codecov/codecov-action@v5
|
uses: codecov/codecov-action@v3
|
||||||
with:
|
with:
|
||||||
files: ${{ inputs.context }}/coverage.xml
|
files: ${{ inputs.context }}/coverage.xml
|
||||||
flags: ${{ inputs.context }}
|
flags: ${{ inputs.context }}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
name: Build container
|
||||||
|
description: Build and push a container
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
target:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
- uses: docker/metadata-action@v4
|
||||||
|
id: meta
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.NAMESPACE }}/${{ inputs.target }}
|
||||||
|
tags: |
|
||||||
|
type=ref,event=branch
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
|
|
||||||
|
- uses: docker/build-push-action@v3
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
pull: true
|
||||||
|
push: ${{ github.event_name == 'push' }}
|
||||||
|
build-args: |
|
||||||
|
LIBRETIME_VERSION=${{ env.LIBRETIME_VERSION }}
|
||||||
|
target: ${{ inputs.target }}
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=gha,scope=${{ inputs.target }}
|
||||||
|
cache-to: type=gha,scope=${{ inputs.target }},mode=max
|
|
@ -1,27 +1,21 @@
|
||||||
name: Analyzer
|
name: Analyzer
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
push:
|
||||||
branches: [main, stable-*]
|
branches: [main]
|
||||||
paths:
|
paths:
|
||||||
- .github/workflows/_python.yml
|
- .github/workflows/_python.yml
|
||||||
- .github/workflows/analyzer.yml
|
- .github/workflows/analyzer.yml
|
||||||
- analyzer/**
|
- analyzer/**
|
||||||
- shared/**
|
- shared/**
|
||||||
- tools/python*
|
|
||||||
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [main, stable-*]
|
branches: [main]
|
||||||
paths:
|
paths:
|
||||||
- .github/workflows/_python.yml
|
- .github/workflows/_python.yml
|
||||||
- .github/workflows/analyzer.yml
|
- .github/workflows/analyzer.yml
|
||||||
- analyzer/**
|
- analyzer/**
|
||||||
- shared/**
|
- shared/**
|
||||||
- tools/python*
|
|
||||||
|
|
||||||
schedule:
|
|
||||||
- cron: 0 1 * * 1
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
python:
|
python:
|
||||||
|
|
|
@ -1,27 +1,21 @@
|
||||||
name: API Client
|
name: API Client
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
push:
|
||||||
branches: [main, stable-*]
|
branches: [main]
|
||||||
paths:
|
paths:
|
||||||
- .github/workflows/_python.yml
|
- .github/workflows/_python.yml
|
||||||
- .github/workflows/api-client.yml
|
- .github/workflows/api-client.yml
|
||||||
- api-client/**
|
- api-client/**
|
||||||
- shared/**
|
- shared/**
|
||||||
- tools/python*
|
|
||||||
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [main, stable-*]
|
branches: [main]
|
||||||
paths:
|
paths:
|
||||||
- .github/workflows/_python.yml
|
- .github/workflows/_python.yml
|
||||||
- .github/workflows/api-client.yml
|
- .github/workflows/api-client.yml
|
||||||
- api-client/**
|
- api-client/**
|
||||||
- shared/**
|
- shared/**
|
||||||
- tools/python*
|
|
||||||
|
|
||||||
schedule:
|
|
||||||
- cron: 0 1 * * 1
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
python:
|
python:
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
name: API schema
|
name: API schema
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
paths:
|
paths:
|
||||||
|
@ -21,15 +20,15 @@ jobs:
|
||||||
check:
|
check:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: "3.x"
|
python-version: "3.x"
|
||||||
|
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/pip
|
path: ~/.cache/pip
|
||||||
key: ${{ runner.os }}-pip-api-${{ hashFiles('api/**/setup.py') }}
|
key: ${{ runner.os }}-pip-api-${{ hashFiles('api/**/setup.py') }}
|
||||||
|
@ -42,7 +41,7 @@ jobs:
|
||||||
|
|
||||||
- name: Get pull request commit range
|
- name: Get pull request commit range
|
||||||
if: github.event_name == 'pull_request'
|
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
|
- name: Get push commit range
|
||||||
if: github.event_name == 'push'
|
if: github.event_name == 'push'
|
||||||
|
@ -54,11 +53,11 @@ jobs:
|
||||||
git checkout $commit
|
git checkout $commit
|
||||||
|
|
||||||
make --quiet schema
|
make --quiet schema
|
||||||
git diff -- schema.yml
|
|
||||||
git add schema.yml
|
git add schema.yml
|
||||||
git diff-index --quiet HEAD -- || {
|
git diff-index --quiet HEAD -- || {
|
||||||
echo "ERROR: Schema is outdated for commit $commit"
|
echo "ERROR: Schema is outdated for commit $commit"
|
||||||
git show --quiet
|
git show --quiet
|
||||||
|
git diff -- schema.yml
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
done
|
done
|
||||||
|
@ -71,11 +70,11 @@ jobs:
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
repository: libretime/client
|
repository: libretime/client
|
||||||
path: client
|
path: client
|
||||||
|
|
|
@ -1,27 +1,21 @@
|
||||||
name: API
|
name: API
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
push:
|
||||||
branches: [main, stable-*]
|
branches: [main]
|
||||||
paths:
|
paths:
|
||||||
- .github/workflows/_python.yml
|
- .github/workflows/_python.yml
|
||||||
- .github/workflows/api.yml
|
- .github/workflows/api.yml
|
||||||
- api/**
|
- api/**
|
||||||
- shared/**
|
- shared/**
|
||||||
- tools/python*
|
|
||||||
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [main, stable-*]
|
branches: [main]
|
||||||
paths:
|
paths:
|
||||||
- .github/workflows/_python.yml
|
- .github/workflows/_python.yml
|
||||||
- .github/workflows/api.yml
|
- .github/workflows/api.yml
|
||||||
- api/**
|
- api/**
|
||||||
- shared/**
|
- shared/**
|
||||||
- tools/python*
|
|
||||||
|
|
||||||
schedule:
|
|
||||||
- cron: 0 1 * * 1
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
python:
|
python:
|
||||||
|
@ -36,8 +30,10 @@ jobs:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
release:
|
release:
|
||||||
- focal
|
- buster
|
||||||
- bullseye
|
- bullseye
|
||||||
|
- bionic
|
||||||
|
- focal
|
||||||
- jammy
|
- jammy
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
@ -62,9 +58,9 @@ jobs:
|
||||||
LIBRETIME_DATABASE_HOST: postgres
|
LIBRETIME_DATABASE_HOST: postgres
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/pip
|
path: ~/.cache/pip
|
||||||
key: ${{ matrix.release }}-pip-api-${{ hashFiles('api/**/setup.py') }}
|
key: ${{ matrix.release }}-pip-api-${{ hashFiles('api/**/setup.py') }}
|
||||||
|
@ -72,11 +68,11 @@ jobs:
|
||||||
${{ matrix.release }}-pip-api
|
${{ matrix.release }}-pip-api
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: make test-coverage
|
run: make test
|
||||||
working-directory: api
|
working-directory: api
|
||||||
|
|
||||||
- name: Report coverage
|
- name: Report coverage
|
||||||
uses: codecov/codecov-action@v5
|
uses: codecov/codecov-action@v3
|
||||||
with:
|
with:
|
||||||
files: api/coverage.xml
|
files: api/coverage.xml
|
||||||
flags: api
|
flags: api
|
||||||
|
|
|
@ -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
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
name: Command
|
||||||
|
|
||||||
|
on:
|
||||||
|
issue_comment:
|
||||||
|
types: [created]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
dispatch:
|
||||||
|
name: Dispatch
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Dispatch website preview
|
||||||
|
uses: peter-evans/slash-command-dispatch@v3
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.COMMAND_DISPATCH_TOKEN }}
|
||||||
|
issue-type: pull-request
|
||||||
|
dispatch-type: workflow
|
||||||
|
commands: website-preview
|
||||||
|
static-args: |
|
||||||
|
pull-request-number=${{ github.event.issue.number }}
|
|
@ -3,101 +3,86 @@ name: Container
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags: ["[0-9]+.[0-9]+.[0-9]+*"]
|
tags: ["[0-9]+.[0-9]+.[0-9]+*"]
|
||||||
branches: [main, stable-*]
|
branches: [main]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [main, stable-*]
|
branches: [main]
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
meta:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
target: [analyzer, api, legacy, playout, worker]
|
|
||||||
|
|
||||||
if: ${{ github.repository_owner == 'libretime' }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Update Docker Hub description
|
|
||||||
if: github.event_name == 'push'
|
|
||||||
uses: peter-evans/dockerhub-description@v4
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
repository: libretime/libretime-${{ matrix.target }}
|
|
||||||
readme-filepath: ./README.md
|
|
||||||
|
|
||||||
- uses: docker/metadata-action@v5
|
|
||||||
id: meta
|
|
||||||
with:
|
|
||||||
bake-target: ${{ matrix.target }}
|
|
||||||
images: |
|
|
||||||
ghcr.io/libretime/libretime-${{ matrix.target }}
|
|
||||||
docker.io/libretime/libretime-${{ matrix.target }}
|
|
||||||
tags: |
|
|
||||||
type=ref,event=branch
|
|
||||||
type=semver,pattern={{version}}
|
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
|
||||||
|
|
||||||
- name: Upload metadata bake file
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: meta-${{ matrix.target }}
|
|
||||||
path: ${{ steps.meta.outputs.bake-file }}
|
|
||||||
|
|
||||||
build:
|
build:
|
||||||
needs: [meta]
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
NAMESPACE: ${{ github.repository_owner }}
|
||||||
|
|
||||||
if: ${{ github.repository_owner == 'libretime' }}
|
if: ${{ github.repository_owner == 'libretime' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- uses: docker/setup-buildx-action@v3
|
- uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
- name: Login ghcr.io
|
- uses: docker/login-action@v2
|
||||||
if: github.event_name == 'push'
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Login docker.io
|
|
||||||
if: github.event_name == 'push'
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: docker.io
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Download all metadata bake files
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
pattern: meta-*
|
|
||||||
|
|
||||||
- name: Guess LIBRETIME_VERSION
|
- name: Guess LIBRETIME_VERSION
|
||||||
run: |
|
run: |
|
||||||
make VERSION
|
make VERSION
|
||||||
echo "LIBRETIME_VERSION=$(cat VERSION | tr -d [:blank:])" >> $GITHUB_ENV
|
echo "LIBRETIME_VERSION=$(cat VERSION | tr -d [:blank:])" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Build
|
- name: Build python-builder
|
||||||
uses: docker/bake-action@v5
|
uses: docker/build-push-action@v3
|
||||||
with:
|
with:
|
||||||
|
context: .
|
||||||
pull: true
|
pull: true
|
||||||
push: ${{ github.event_name == 'push' }}
|
target: python-builder
|
||||||
files: |
|
cache-from: type=gha,scope=python-builder
|
||||||
docker-bake.json
|
cache-to: type=gha,scope=python-builder,mode=max
|
||||||
meta-analyzer/docker-metadata-action-bake.json
|
|
||||||
meta-api/docker-metadata-action-bake.json
|
- name: Build python-base
|
||||||
meta-legacy/docker-metadata-action-bake.json
|
uses: docker/build-push-action@v3
|
||||||
meta-playout/docker-metadata-action-bake.json
|
with:
|
||||||
meta-worker/docker-metadata-action-bake.json
|
context: .
|
||||||
set: |
|
pull: true
|
||||||
*.cache-from=type=gha,scope=container
|
target: python-base
|
||||||
*.cache-to=type=gha,scope=container,mode=max
|
cache-from: type=gha,scope=python-base
|
||||||
*.args.LIBRETIME_VERSION=${{ env.LIBRETIME_VERSION }}
|
cache-to: type=gha,scope=python-base,mode=max
|
||||||
|
|
||||||
|
- name: Build python-base-ffmpeg
|
||||||
|
uses: docker/build-push-action@v3
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
pull: true
|
||||||
|
target: python-base-ffmpeg
|
||||||
|
cache-from: type=gha,scope=python-base-ffmpeg
|
||||||
|
cache-to: type=gha,scope=python-base-ffmpeg,mode=max
|
||||||
|
|
||||||
|
- name: Build analyzer
|
||||||
|
uses: ./.github/workflows/actions/build-container
|
||||||
|
with:
|
||||||
|
target: libretime-analyzer
|
||||||
|
|
||||||
|
- name: Build api
|
||||||
|
uses: ./.github/workflows/actions/build-container
|
||||||
|
with:
|
||||||
|
target: libretime-api
|
||||||
|
|
||||||
|
- name: Build playout
|
||||||
|
uses: ./.github/workflows/actions/build-container
|
||||||
|
with:
|
||||||
|
target: libretime-playout
|
||||||
|
|
||||||
|
- name: Build worker
|
||||||
|
uses: ./.github/workflows/actions/build-container
|
||||||
|
with:
|
||||||
|
target: libretime-worker
|
||||||
|
|
||||||
|
- name: Build legacy
|
||||||
|
uses: ./.github/workflows/actions/build-container
|
||||||
|
with:
|
||||||
|
target: libretime-legacy
|
||||||
|
|
|
@ -15,12 +15,16 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
|
- distribution: ubuntu
|
||||||
|
release: bionic
|
||||||
- distribution: ubuntu
|
- distribution: ubuntu
|
||||||
release: focal
|
release: focal
|
||||||
- distribution: debian
|
|
||||||
release: bullseye
|
|
||||||
- distribution: ubuntu
|
- distribution: ubuntu
|
||||||
release: jammy
|
release: jammy
|
||||||
|
- distribution: debian
|
||||||
|
release: buster
|
||||||
|
- distribution: debian
|
||||||
|
release: bullseye
|
||||||
- distribution: debian
|
- distribution: debian
|
||||||
release: bookworm
|
release: bookworm
|
||||||
|
|
||||||
|
@ -30,10 +34,10 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Login to the Container registry
|
- name: Login to the Container registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
|
@ -55,7 +59,7 @@ jobs:
|
||||||
COPY packages.list packages.list
|
COPY packages.list packages.list
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
[[ "${{ matrix.release }}" == "focal" ]] && \
|
[[ "${{ matrix.release }}" =~ "bionic|focal" ]] && \
|
||||||
cat <<EOF >> Dockerfile
|
cat <<EOF >> Dockerfile
|
||||||
RUN DEBIAN_FRONTEND=noninteractive apt-get --quiet update && \
|
RUN DEBIAN_FRONTEND=noninteractive apt-get --quiet update && \
|
||||||
DEBIAN_FRONTEND=noninteractive apt-get --quiet install -y software-properties-common && \
|
DEBIAN_FRONTEND=noninteractive apt-get --quiet install -y software-properties-common && \
|
||||||
|
@ -78,7 +82,7 @@ jobs:
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v3
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: ${{ github.repository_owner == 'libretime' }}
|
push: ${{ github.repository_owner == 'libretime' }}
|
||||||
|
|
|
@ -2,21 +2,20 @@ name: Docs
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main, stable-*]
|
branches: [main]
|
||||||
paths:
|
paths:
|
||||||
- .github/vale/**
|
- .github/vale/**
|
||||||
- .github/workflows/docs.yml
|
- .github/workflows/docs.yml
|
||||||
- docs/**
|
- docs/**
|
||||||
|
- website/**
|
||||||
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [main, stable-*]
|
branches: [main]
|
||||||
paths:
|
paths:
|
||||||
- .github/vale/**
|
- .github/vale/**
|
||||||
- .github/workflows/docs.yml
|
- .github/workflows/docs.yml
|
||||||
- docs/**
|
- docs/**
|
||||||
|
- website/**
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
|
@ -24,9 +23,9 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
/usr/local/bin/vale*
|
/usr/local/bin/vale*
|
||||||
|
@ -43,7 +42,6 @@ jobs:
|
||||||
errata-ai/vale \
|
errata-ai/vale \
|
||||||
vale_{version}_Linux_64-bit.tar.gz --extract vale \
|
vale_{version}_Linux_64-bit.tar.gz --extract vale \
|
||||||
/usr/local/bin/vale \
|
/usr/local/bin/vale \
|
||||||
--version v2.21.3 \
|
|
||||||
--version-file '{destination}.version'
|
--version-file '{destination}.version'
|
||||||
|
|
||||||
- name: Add annotations matchers
|
- name: Add annotations matchers
|
||||||
|
@ -53,26 +51,5 @@ jobs:
|
||||||
- name: Run Vale
|
- name: Run Vale
|
||||||
run: |
|
run: |
|
||||||
vale sync
|
vale sync
|
||||||
vale --output line docs || true
|
vale --output line docs website/src/pages || true
|
||||||
vale --output line --minAlertLevel=error docs/releases
|
vale --output line --minAlertLevel=error docs/releases
|
||||||
|
|
||||||
sync:
|
|
||||||
name: Sync
|
|
||||||
if: >
|
|
||||||
github.repository_owner == 'libretime' &&
|
|
||||||
github.event_name == 'push'
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
repository: libretime/website
|
|
||||||
path: website
|
|
||||||
ssh-key: "${{ secrets.WEBSITE_DEPLOY_KEY }}"
|
|
||||||
|
|
||||||
- name: Sync docs changes
|
|
||||||
run: tools/ci-sync-docs.sh ${{ github.event.before }}..${{ github.sha }}
|
|
||||||
|
|
|
@ -10,9 +10,6 @@ on:
|
||||||
required: true
|
required: true
|
||||||
default: "5"
|
default: "5"
|
||||||
|
|
||||||
permissions:
|
|
||||||
issues: write
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
find_closed_references:
|
find_closed_references:
|
||||||
if: github.repository_owner == 'libretime'
|
if: github.repository_owner == 'libretime'
|
||||||
|
@ -20,9 +17,9 @@ jobs:
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: "16"
|
node-version: "16"
|
||||||
|
|
||||||
|
@ -30,7 +27,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
issueLimit: ${{ github.event.inputs.issueLimit || '5' }}
|
issueLimit: ${{ github.event.inputs.issueLimit || '5' }}
|
||||||
ignore: .git,/docs/releases/*,CHANGELOG.md
|
ignore: .git,/docs/releases/*,/website/versioned*,CHANGELOG.md
|
||||||
|
|
||||||
find_broken_links:
|
find_broken_links:
|
||||||
if: github.repository_owner == 'libretime'
|
if: github.repository_owner == 'libretime'
|
||||||
|
@ -38,9 +35,9 @@ jobs:
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: .lycheecache
|
path: .lycheecache
|
||||||
key: housekeeping-find-broken-links-${{ github.sha }}
|
key: housekeeping-find-broken-links-${{ github.sha }}
|
||||||
|
@ -48,59 +45,23 @@ jobs:
|
||||||
|
|
||||||
- name: Check Links
|
- name: Check Links
|
||||||
id: lychee
|
id: lychee
|
||||||
uses: lycheeverse/lychee-action@v2.1.0
|
uses: lycheeverse/lychee-action@v1.5.1
|
||||||
with:
|
with:
|
||||||
args: >-
|
args: >-
|
||||||
'**/*.md'
|
'**/*.md'
|
||||||
|
--exclude-path 'website/versioned_docs'
|
||||||
--require-https
|
--require-https
|
||||||
--exclude-all-private
|
--exclude-all-private
|
||||||
|
--exclude-mail
|
||||||
--exclude 'example\.(com|org)'
|
--exclude 'example\.(com|org)'
|
||||||
--exclude '\$server_name\$request_uri'
|
--exclude '\$server_name\$request_uri'
|
||||||
--exclude '%7Bvars.version%7D'
|
--exclude '%7Bvars.version%7D'
|
||||||
--exclude 'https://dir.xiph.org/cgi-bin/yp-cgi'
|
--exclude 'https://dir.xiph.org/cgi-bin/yp-cgi'
|
||||||
--exclude 'https://radio.indymedia.org/cgi-bin/yp-cgi'
|
--exclude 'https://radio.indymedia.org/cgi-bin/yp-cgi'
|
||||||
--exclude 'https://www.ascap.com'
|
--exclude 'https://www.ascap.com'
|
||||||
--exclude 'https://www.youtube-nocookie.com'
|
|
||||||
--exclude 'github\.com/libretime/libretime/(issues|pulls)'
|
--exclude 'github\.com/libretime/libretime/(issues|pulls)'
|
||||||
--exclude 'https://packages.ubuntu.com/bionic/php7.2'
|
|
||||||
--exclude 'https://packages.ubuntu.com/bionic/python3'
|
|
||||||
--cache
|
--cache
|
||||||
--max-cache-age 2d
|
--max-cache-age 2d
|
||||||
fail: true
|
fail: true
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
find_stale_issues:
|
|
||||||
if: github.repository_owner == 'libretime'
|
|
||||||
name: Find stale issues
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/stale@v9
|
|
||||||
with:
|
|
||||||
stale-issue-message: >
|
|
||||||
This issue has been automatically marked as stale because it has not had
|
|
||||||
activity in the last 5 months. It will be closed if no activity occurs in
|
|
||||||
the next month.
|
|
||||||
|
|
||||||
Please chat to us on the [forum](https://discourse.libretime.org/) or
|
|
||||||
ask for help on [#libretime:matrix.org](https://matrix.to/#/#libretime:matrix.org)
|
|
||||||
if you have any questions or need further support with getting this issue resolved.
|
|
||||||
|
|
||||||
You may also label an issue as *pinned* if you would like to make sure
|
|
||||||
that it does not get closed by this bot.
|
|
||||||
close-issue-message: >
|
|
||||||
This issue has been automatically closed after is was marked as stale and
|
|
||||||
did not receive any further inputs.
|
|
||||||
|
|
||||||
Feel free to let us know on the [forum](https://discourse.libretime.org/) or
|
|
||||||
ask for help on [#libretime:matrix.org](https://matrix.to/#/#libretime:matrix.org)
|
|
||||||
if you feel this issue should not have been closed.
|
|
||||||
|
|
||||||
Thank you for your contributions.
|
|
||||||
days-before-issue-stale: 150
|
|
||||||
days-before-issue-close: 30
|
|
||||||
stale-issue-label: "status: stalled"
|
|
||||||
exempt-issue-labels: "status: pinned,status: maybe later,security,is: feature-request"
|
|
||||||
exempt-issue-assignees: true
|
|
||||||
exempt-issue-milestones: true
|
|
||||||
|
|
|
@ -1,24 +1,20 @@
|
||||||
name: Legacy
|
name: Legacy
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
push:
|
||||||
branches: [main, stable-*]
|
branches: [main]
|
||||||
paths:
|
paths:
|
||||||
- .github/workflows/legacy.yml
|
- .github/workflows/legacy.yml
|
||||||
- api/**
|
- api/**
|
||||||
- legacy/**
|
- legacy/**
|
||||||
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [main, stable-*]
|
branches: [main]
|
||||||
paths:
|
paths:
|
||||||
- .github/workflows/legacy.yml
|
- .github/workflows/legacy.yml
|
||||||
- api/**
|
- api/**
|
||||||
- legacy/**
|
- legacy/**
|
||||||
|
|
||||||
schedule:
|
|
||||||
- cron: 0 1 * * 1
|
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
@ -30,10 +26,12 @@ jobs:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- php-version: "7.4" # Focal, Bullseye
|
- php-version: "7.2" # Bionic
|
||||||
|
- php-version: "7.3" # Buster
|
||||||
|
- php-version: "7.4" # Bullseye, Focal
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
- uses: shivammathur/setup-php@v2
|
- uses: shivammathur/setup-php@v2
|
||||||
with:
|
with:
|
||||||
php-version: ${{ matrix.php-version }}
|
php-version: ${{ matrix.php-version }}
|
||||||
|
@ -48,12 +46,14 @@ jobs:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- php-version: "7.4" # Focal, Bullseye
|
- php-version: "7.2" # Bionic
|
||||||
|
- php-version: "7.3" # Buster
|
||||||
|
- php-version: "7.4" # Bullseye, Focal
|
||||||
|
|
||||||
env:
|
env:
|
||||||
ENVIRONMENT: testing
|
ENVIRONMENT: testing
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup PostgreSQL
|
- name: Setup PostgreSQL
|
||||||
run: |
|
run: |
|
||||||
|
@ -62,7 +62,6 @@ jobs:
|
||||||
sudo -u postgres psql -c 'CREATE DATABASE libretime;'
|
sudo -u postgres psql -c 'CREATE DATABASE libretime;'
|
||||||
sudo -u postgres psql -c "CREATE USER libretime WITH PASSWORD '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 '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;'
|
sudo -u postgres psql -c 'ALTER USER libretime CREATEDB;'
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
|
@ -73,9 +72,9 @@ jobs:
|
||||||
- name: Get Composer Cache Directory
|
- name: Get Composer Cache Directory
|
||||||
id: composer-cache
|
id: composer-cache
|
||||||
run: |
|
run: |
|
||||||
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
echo "::set-output name=dir::$(composer config cache-files-dir)"
|
||||||
|
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.composer-cache.outputs.dir }}
|
path: ${{ steps.composer-cache.outputs.dir }}
|
||||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||||
|
@ -85,36 +84,3 @@ jobs:
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: make test
|
run: make test
|
||||||
working-directory: legacy
|
working-directory: legacy
|
||||||
|
|
||||||
locale:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
if: >
|
|
||||||
github.repository_owner == 'libretime' && (
|
|
||||||
github.event_name == 'schedule' ||
|
|
||||||
github.event_name == 'workflow_dispatch'
|
|
||||||
)
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.LIBRETIME_BOT_TOKEN }}
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
DEBIAN_FRONTEND=noninteractive sudo apt-get update
|
|
||||||
DEBIAN_FRONTEND=noninteractive sudo apt-get install -y gettext
|
|
||||||
|
|
||||||
- name: Update locales
|
|
||||||
run: |
|
|
||||||
git config --global user.name "libretime-bot"
|
|
||||||
git config --global user.email "libretime-bot@users.noreply.github.com"
|
|
||||||
|
|
||||||
git pull
|
|
||||||
|
|
||||||
make -C legacy/locale update
|
|
||||||
|
|
||||||
git add legacy/locale
|
|
||||||
git diff-index --quiet HEAD -- legacy/locale || {
|
|
||||||
git commit --message "chore(legacy): update locales"
|
|
||||||
git push
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,29 +1,23 @@
|
||||||
name: Playout
|
name: Playout
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
push:
|
||||||
branches: [main, stable-*]
|
branches: [main]
|
||||||
paths:
|
paths:
|
||||||
- .github/workflows/_python.yml
|
- .github/workflows/_python.yml
|
||||||
- .github/workflows/playout.yml
|
- .github/workflows/playout.yml
|
||||||
- playout/**
|
- playout/**
|
||||||
- api-client/**
|
- api-client/**
|
||||||
- shared/**
|
- shared/**
|
||||||
- tools/python*
|
|
||||||
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [main, stable-*]
|
branches: [main]
|
||||||
paths:
|
paths:
|
||||||
- .github/workflows/_python.yml
|
- .github/workflows/_python.yml
|
||||||
- .github/workflows/playout.yml
|
- .github/workflows/playout.yml
|
||||||
- playout/**
|
- playout/**
|
||||||
- api-client/**
|
- api-client/**
|
||||||
- shared/**
|
- shared/**
|
||||||
- tools/python*
|
|
||||||
|
|
||||||
schedule:
|
|
||||||
- cron: 0 1 * * 1
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
python:
|
python:
|
||||||
|
|
|
@ -12,7 +12,7 @@ jobs:
|
||||||
name: Validate PR title
|
name: Validate PR title
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: amannn/action-semantic-pull-request@v5.5.3
|
- uses: amannn/action-semantic-pull-request@v4.6.0
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
|
@ -27,8 +27,6 @@ jobs:
|
||||||
ui
|
ui
|
||||||
worker
|
worker
|
||||||
deps
|
deps
|
||||||
main
|
|
||||||
stable
|
|
||||||
subjectPattern: ^(?![A-Z]).+$
|
subjectPattern: ^(?![A-Z]).+$
|
||||||
subjectPatternError: |
|
subjectPatternError: |
|
||||||
The subject "{subject}" found in the pull request title "{title}"
|
The subject "{subject}" found in the pull request title "{title}"
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
name: Project
|
name: Project
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
push:
|
||||||
branches: [main, stable-*]
|
branches: [main]
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, reopened, synchronize, edited]
|
types: [opened, reopened, synchronize, edited]
|
||||||
branches: [main, stable-*]
|
branches: [main]
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
@ -15,26 +14,67 @@ jobs:
|
||||||
pre-commit:
|
pre-commit:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-python@v4
|
||||||
- uses: actions/setup-python@v5
|
|
||||||
with:
|
with:
|
||||||
python-version: "3.x"
|
python-version: "3.x"
|
||||||
|
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/pip
|
path: ~/.cache/pip
|
||||||
key: ${{ runner.os }}-project-pre-commit-pip-${{ hashFiles('.pre-commit-config.yaml') }}
|
key: ${{ runner.os }}-project-pre-commit-pip-${{ hashFiles('.pre-commit-config.yaml') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-project-pre-commit-pip
|
${{ runner.os }}-project-pre-commit-pip
|
||||||
|
|
||||||
- uses: pre-commit/action@v3.0.1
|
- uses: pre-commit/action@v3.0.0
|
||||||
|
|
||||||
|
check-shell:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: "3.x"
|
||||||
|
|
||||||
|
- uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ~/.cache/pip
|
||||||
|
key: ${{ runner.os }}-project-check-shell-pip
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-project-check-shell-pip
|
||||||
|
|
||||||
|
- uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
/usr/local/bin/shellcheck*
|
||||||
|
/usr/local/bin/shfmt*
|
||||||
|
key: ${{ runner.os }}-project-check-shell-tools
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-project-check-shell-tools
|
||||||
|
|
||||||
|
- run: |
|
||||||
|
python -m venv venv && source venv/bin/activate
|
||||||
|
pip install gh-release-install
|
||||||
|
|
||||||
|
sudo venv/bin/gh-release-install \
|
||||||
|
koalaman/shellcheck \
|
||||||
|
shellcheck-{tag}.linux.x86_64.tar.xz --extract shellcheck-{tag}/shellcheck \
|
||||||
|
/usr/local/bin/shellcheck \
|
||||||
|
--version-file '{destination}.version'
|
||||||
|
|
||||||
|
sudo venv/bin/gh-release-install \
|
||||||
|
mvdan/sh \
|
||||||
|
shfmt_{tag}_linux_amd64 \
|
||||||
|
/usr/local/bin/shfmt \
|
||||||
|
--version-file '{destination}.version'
|
||||||
|
|
||||||
|
- run: SEVERITY=warning make shell-check
|
||||||
|
|
||||||
test-tools:
|
test-tools:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: "3.x"
|
python-version: "3.x"
|
||||||
- run: make all
|
- run: make all
|
||||||
|
|
|
@ -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 }}
|
|
|
@ -9,7 +9,7 @@ jobs:
|
||||||
release:
|
release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
- uses: shivammathur/setup-php@v2
|
- uses: shivammathur/setup-php@v2
|
||||||
with:
|
with:
|
||||||
php-version: 7.4
|
php-version: 7.4
|
||||||
|
@ -22,9 +22,10 @@ jobs:
|
||||||
- name: Build tarball
|
- name: Build tarball
|
||||||
run: make tarball
|
run: make tarball
|
||||||
|
|
||||||
- name: Upload tarball
|
- name: Create Release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
files: |
|
body_path: docs/releases/${{ github.ref_name }}.md
|
||||||
libretime-*.tar.gz
|
draft: true
|
||||||
sha256sums.txt
|
prerelease: true
|
||||||
|
files: libretime-*.tar.gz
|
||||||
|
|
|
@ -1,25 +1,19 @@
|
||||||
name: Shared
|
name: Shared
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
push:
|
||||||
branches: [main, stable-*]
|
branches: [main]
|
||||||
paths:
|
paths:
|
||||||
- .github/workflows/_python.yml
|
- .github/workflows/_python.yml
|
||||||
- .github/workflows/shared.yml
|
- .github/workflows/shared.yml
|
||||||
- shared/**
|
- shared/**
|
||||||
- tools/python*
|
|
||||||
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [main, stable-*]
|
branches: [main]
|
||||||
paths:
|
paths:
|
||||||
- .github/workflows/_python.yml
|
- .github/workflows/_python.yml
|
||||||
- .github/workflows/shared.yml
|
- .github/workflows/shared.yml
|
||||||
- shared/**
|
- shared/**
|
||||||
- tools/python*
|
|
||||||
|
|
||||||
schedule:
|
|
||||||
- cron: 0 1 * * 1
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
python:
|
python:
|
||||||
|
|
|
@ -0,0 +1,118 @@
|
||||||
|
name: Website Preview
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
pull-request-number:
|
||||||
|
description: "Pull request number to preview"
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
pull_request_target:
|
||||||
|
types: [closed]
|
||||||
|
branches: [main]
|
||||||
|
paths:
|
||||||
|
- website/**
|
||||||
|
- docs/**
|
||||||
|
|
||||||
|
env:
|
||||||
|
# PREVIEW_DEPLOY_KEY is present in the secrets
|
||||||
|
PREVIEW_EXTERNAL_REPOSITORY: libretime/libretime.github.io
|
||||||
|
PREVIEW_EXTERNAL_REPOSITORY_BRANCH: gh-pages
|
||||||
|
PREVIEW_URL: https://libretime.github.io
|
||||||
|
PREVIEW_BASE_URL: /
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
if: github.event_name == 'workflow_dispatch'
|
||||||
|
name: Deploy
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Checkout pull request
|
||||||
|
run: hub pr checkout ${{ github.event.inputs.pull-request-number }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: "16"
|
||||||
|
cache: yarn
|
||||||
|
cache-dependency-path: website/yarn.lock
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
working-directory: website
|
||||||
|
run: yarn install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
working-directory: website
|
||||||
|
run: yarn build
|
||||||
|
env:
|
||||||
|
URL: ${{ env.PREVIEW_URL }}
|
||||||
|
BASE_URL: ${{ env.PREVIEW_BASE_URL }}pr-${{ github.event.inputs.pull-request-number }}/
|
||||||
|
|
||||||
|
- name: Deploy
|
||||||
|
uses: peaceiris/actions-gh-pages@v3
|
||||||
|
with:
|
||||||
|
external_repository: ${{ env.PREVIEW_EXTERNAL_REPOSITORY }}
|
||||||
|
deploy_key: ${{ secrets.PREVIEW_DEPLOY_KEY }}
|
||||||
|
publish_dir: website/build
|
||||||
|
destination_dir: pr-${{ github.event.inputs.pull-request-number }}
|
||||||
|
full_commit_message: "deploy pr-${{ github.event.inputs.pull-request-number }}"
|
||||||
|
keep_files: true
|
||||||
|
|
||||||
|
- name: Find deployment comment
|
||||||
|
uses: peter-evans/find-comment@v2
|
||||||
|
id: find-comment
|
||||||
|
with:
|
||||||
|
issue-number: ${{ github.event.inputs.pull-request-number }}
|
||||||
|
comment-author: github-actions[bot]
|
||||||
|
body-includes: Website preview deployment
|
||||||
|
|
||||||
|
- name: Notify deployment succeeded
|
||||||
|
if: ${{ success() }}
|
||||||
|
uses: peter-evans/create-or-update-comment@v2
|
||||||
|
with:
|
||||||
|
issue-number: ${{ github.event.inputs.pull-request-number }}
|
||||||
|
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||||
|
edit-mode: replace
|
||||||
|
body: |
|
||||||
|
**:rocket: Website preview deployment succeeded!**
|
||||||
|
|
||||||
|
Website preview: ${{ env.PREVIEW_URL }}${{ env.PREVIEW_BASE_URL }}pr-${{ github.event.inputs.pull-request-number }}/
|
||||||
|
New docs preview: ${{ env.PREVIEW_URL }}${{ env.PREVIEW_BASE_URL }}pr-${{ github.event.inputs.pull-request-number }}/docs/next/
|
||||||
|
Workflow: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||||
|
|
||||||
|
- name: Notify deployment failed
|
||||||
|
if: ${{ failure() }}
|
||||||
|
uses: peter-evans/create-or-update-comment@v2
|
||||||
|
with:
|
||||||
|
issue-number: ${{ github.event.inputs.pull-request-number }}
|
||||||
|
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||||
|
edit-mode: replace
|
||||||
|
body: |
|
||||||
|
**:boom: Website preview deployment failed!**
|
||||||
|
|
||||||
|
Workflow: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||||
|
|
||||||
|
clean:
|
||||||
|
if: github.event_name == 'pull_request_target' && github.event.action == 'closed'
|
||||||
|
name: Clean
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
repository: ${{ env.PREVIEW_EXTERNAL_REPOSITORY }}
|
||||||
|
ref: ${{ env.PREVIEW_EXTERNAL_REPOSITORY_BRANCH }}
|
||||||
|
ssh-key: ${{ secrets.PREVIEW_DEPLOY_KEY }}
|
||||||
|
|
||||||
|
- name: Remove files
|
||||||
|
run: rm -fR pr-${{ github.event.pull_request.number }}
|
||||||
|
|
||||||
|
- uses: endbug/add-and-commit@v9
|
||||||
|
with:
|
||||||
|
message: "clean pr-${{ github.event.pull_request.number }}"
|
|
@ -0,0 +1,53 @@
|
||||||
|
name: Website
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
paths:
|
||||||
|
- .github/workflows/website.yml
|
||||||
|
- docs/**
|
||||||
|
- website/**
|
||||||
|
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
paths:
|
||||||
|
- .github/workflows/website.yml
|
||||||
|
- docs/**
|
||||||
|
- website/**
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
name: Deploy
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: "16"
|
||||||
|
cache: yarn
|
||||||
|
cache-dependency-path: ./website/yarn.lock
|
||||||
|
|
||||||
|
- uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: website/.docusaurus
|
||||||
|
key: docusaurus-main-${{ github.sha }}
|
||||||
|
restore-keys: |
|
||||||
|
docusaurus-main-
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
working-directory: website
|
||||||
|
run: yarn install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
working-directory: website
|
||||||
|
run: yarn build
|
||||||
|
|
||||||
|
- name: Deploy
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
|
uses: peaceiris/actions-gh-pages@v3
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
publish_dir: website/build
|
|
@ -1,25 +1,19 @@
|
||||||
name: Worker
|
name: Worker
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
push:
|
||||||
branches: [main, stable-*]
|
branches: [main]
|
||||||
paths:
|
paths:
|
||||||
- .github/workflows/_python.yml
|
- .github/workflows/_python.yml
|
||||||
- .github/workflows/worker.yml
|
- .github/workflows/worker.yml
|
||||||
- worker/**
|
- worker/**
|
||||||
- tools/python*
|
|
||||||
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [main, stable-*]
|
branches: [main]
|
||||||
paths:
|
paths:
|
||||||
- .github/workflows/_python.yml
|
- .github/workflows/_python.yml
|
||||||
- .github/workflows/worker.yml
|
- .github/workflows/worker.yml
|
||||||
- worker/**
|
- worker/**
|
||||||
- tools/python*
|
|
||||||
|
|
||||||
schedule:
|
|
||||||
- cron: 0 1 * * 1
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
python:
|
python:
|
||||||
|
|
|
@ -8,13 +8,6 @@
|
||||||
*~
|
*~
|
||||||
VERSION
|
VERSION
|
||||||
|
|
||||||
/dev/certs/*
|
|
||||||
/dev/playout/*
|
|
||||||
|
|
||||||
/website/
|
|
||||||
|
|
||||||
!.gitkeep
|
|
||||||
|
|
||||||
## Github Python .gitignore
|
## Github Python .gitignore
|
||||||
## See https://github.com/github/gitignore/blob/master/Python.gitignore
|
## See https://github.com/github/gitignore/blob/master/Python.gitignore
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
# See https://pre-commit.com/hooks.html for more hooks
|
# See https://pre-commit.com/hooks.html for more hooks
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v5.0.0
|
rev: v4.3.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-added-large-files
|
- id: check-added-large-files
|
||||||
- id: check-case-conflict
|
- id: check-case-conflict
|
||||||
|
@ -26,63 +26,48 @@ repos:
|
||||||
exclude: \.ambr$
|
exclude: \.ambr$
|
||||||
|
|
||||||
- id: name-tests-test
|
- id: name-tests-test
|
||||||
exclude: ^api
|
# TODO: Remove once the django api uses pytest
|
||||||
|
exclude: ^(api.*)$
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||||
rev: v3.1.0
|
rev: v2.7.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: prettier
|
- id: prettier
|
||||||
files: \.(md|mdx|yml|yaml|js|jsx|ts|tsx|json|css)$
|
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
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
rev: v3.19.0
|
rev: v2.38.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
args: [--py38-plus]
|
args: [--py3-plus, --py36-plus]
|
||||||
|
|
||||||
- repo: https://github.com/adamchainz/django-upgrade
|
- repo: https://github.com/psf/black
|
||||||
rev: 1.22.2
|
rev: 22.8.0
|
||||||
hooks:
|
|
||||||
- id: django-upgrade
|
|
||||||
args: [--target-version, "4.2"]
|
|
||||||
|
|
||||||
- repo: https://github.com/pycqa/isort
|
|
||||||
rev: 5.13.2
|
|
||||||
hooks:
|
|
||||||
- id: isort
|
|
||||||
args: [--resolve-all-configs]
|
|
||||||
|
|
||||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
|
||||||
rev: 24.10.0
|
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
|
|
||||||
|
# - repo: https://github.com/pycqa/isort
|
||||||
|
# rev: 5.9.3
|
||||||
|
# hooks:
|
||||||
|
# - id: isort
|
||||||
|
# args: ["--profile", "black", "--filter-files"]
|
||||||
|
|
||||||
- repo: https://github.com/codespell-project/codespell
|
- repo: https://github.com/codespell-project/codespell
|
||||||
rev: v2.3.0
|
rev: v2.2.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: codespell
|
- id: codespell
|
||||||
args: [--ignore-words=.codespellignore]
|
args:
|
||||||
|
- --ignore-words=.codespellignore
|
||||||
|
- --builtin=clear,rare,informal
|
||||||
exclude: (^api/schema.yml|^legacy.*|yarn\.lock)$
|
exclude: (^api/schema.yml|^legacy.*|yarn\.lock)$
|
||||||
|
|
||||||
- repo: local
|
- repo: local
|
||||||
hooks:
|
hooks:
|
||||||
- id: shfmt
|
|
||||||
name: shfmt
|
|
||||||
language: docker_image
|
|
||||||
entry: mvdan/shfmt -i 2 -ci -sr -kp -w
|
|
||||||
types: [shell]
|
|
||||||
|
|
||||||
- id: shellcheck
|
|
||||||
name: shellcheck
|
|
||||||
language: docker_image
|
|
||||||
entry: koalaman/shellcheck --color=always --severity=warning
|
|
||||||
types: [shell]
|
|
||||||
|
|
||||||
- id: requirements.txt
|
- id: requirements.txt
|
||||||
name: requirements.txt
|
name: requirements.txt
|
||||||
description: Generate requirements.txt
|
description: Generate requirements.txt
|
||||||
entry: tools/extract_requirements.py dev sentry
|
entry: tools/extract_requirements.py dev
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
language: script
|
language: script
|
||||||
files: setup.py$
|
files: setup.py$
|
||||||
|
@ -95,14 +80,6 @@ repos:
|
||||||
language: script
|
language: script
|
||||||
files: ^installer/config.yml$
|
files: ^installer/config.yml$
|
||||||
|
|
||||||
- id: legacy-migrations-version
|
|
||||||
name: legacy-migrations-version
|
|
||||||
description: Ensure valid schema version for migrations
|
|
||||||
entry: tools/legacy-migrations-version.sh
|
|
||||||
pass_filenames: false
|
|
||||||
language: script
|
|
||||||
files: ^api/libretime_api/legacy/migrations
|
|
||||||
|
|
||||||
- id: legacy-assets-checksum-update
|
- id: legacy-assets-checksum-update
|
||||||
name: legacy-assets-checksum-update
|
name: legacy-assets-checksum-update
|
||||||
description: Update legacy assets checksum
|
description: Update legacy assets checksum
|
||||||
|
@ -110,11 +87,3 @@ repos:
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
language: script
|
language: script
|
||||||
files: ^legacy
|
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
|
|
||||||
|
|
|
@ -2,16 +2,17 @@ StylesPath = .github/vale/styles
|
||||||
MinAlertLevel = warning
|
MinAlertLevel = warning
|
||||||
|
|
||||||
Packages = \
|
Packages = \
|
||||||
|
https://github.com/errata-ai/Google/releases/latest/download/Google.zip, \
|
||||||
https://github.com/errata-ai/Microsoft/releases/latest/download/Microsoft.zip
|
https://github.com/errata-ai/Microsoft/releases/latest/download/Microsoft.zip
|
||||||
|
|
||||||
Vocab = Docs
|
Vocab = Docs
|
||||||
|
|
||||||
[*.md]
|
[*.md]
|
||||||
BasedOnStyles = Vale, Microsoft, LibreTime
|
BasedOnStyles = Vale, Google, Microsoft, LibreTime
|
||||||
|
|
||||||
# Exclude emoji shortcodes `:tada:`
|
# Exclude emoji shortcodes `:tada:`
|
||||||
BlockIgnores = (:[a-z-_]+:)
|
BlockIgnores = (:[a-z-_]+:)
|
||||||
|
|
||||||
|
Google.Units = False
|
||||||
Microsoft.GeneralURL = False
|
Microsoft.GeneralURL = False
|
||||||
Microsoft.RangeFormat = False
|
|
||||||
Vale.Spelling = False
|
Vale.Spelling = False
|
||||||
|
|
443
CHANGELOG.md
443
CHANGELOG.md
|
@ -1,437 +1,4 @@
|
||||||
# Changelog
|
<a name="3.0.0-beta.1"></a>
|
||||||
|
|
||||||
## [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 <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 <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 <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 <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 <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 <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 <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 <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
|
|
||||||
|
|
||||||
## [3.1.0](https://github.com/libretime/libretime/compare/3.0.2...3.1.0) (2023-05-26)
|
|
||||||
|
|
||||||
- [Release note](https://libretime.org/docs/releases/3.1.0/)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- drop Ubuntu Bionic support
|
|
||||||
- drop Python 3.6 support
|
|
||||||
- drop Debian Buster support
|
|
||||||
- drop Liquidsoap 1.1 support
|
|
||||||
- drop Liquidsoap 1.3 support
|
|
||||||
- drop Python 3.7 support
|
|
||||||
- drop cc_stream_setting table
|
|
||||||
- delete cc_pref stream preferences rows
|
|
||||||
- **legacy:** remove db allowed_cors_origins preference ([#2095](https://github.com/libretime/libretime/issues/2095))
|
|
||||||
- configure cue points analysis per track type
|
|
||||||
- **playout:** use jinja2 env for template loading
|
|
||||||
- **playout:** add jinja2 quote filter for liquidsoap
|
|
||||||
- **playout:** use liquidsoap interactive variables
|
|
||||||
- **playout:** remove unused liquidsoap outputs connection status
|
|
||||||
- **playout:** remove unused liquidsoap restart function
|
|
||||||
- **playout:** remove unused liquidsoap output namespace
|
|
||||||
- replace loguru with logging
|
|
||||||
- **playout:** use jinja to configure liquidsoap outputs
|
|
||||||
- **playout:** enable vorbis metadata per icecast output
|
|
||||||
- **playout:** use shared app for cli commands
|
|
||||||
- **installer:** configure timezone using timedatectl ([#2418](https://github.com/libretime/libretime/issues/2418))
|
|
||||||
- **playout:** don't serialize message twice
|
|
||||||
- add python packages version
|
|
||||||
- add sentry sdk
|
|
||||||
- use secret_key config field instead of api_key ([#2444](https://github.com/libretime/libretime/issues/2444))
|
|
||||||
- **api-client:** remove unused api v1 calls
|
|
||||||
- **api-client:** rewrite api-client v1 using abstract client
|
|
||||||
- **playout:** move liquidsoap auth to notify cli
|
|
||||||
- **playout:** replace schedule event dicts with objects
|
|
||||||
- **api:** add cors headers middleware ([#2479](https://github.com/libretime/libretime/issues/2479))
|
|
||||||
- **playout:** replace thread timeout with socket timeout
|
|
||||||
- remove dev files from tarball
|
|
||||||
- include tarball checksums in releases
|
|
||||||
- set icecast mount default charset to UTF-8
|
|
||||||
- **playout:** allow harbor ssl configuration
|
|
||||||
- **api:** install gunicorn/uvicorn from pip
|
|
||||||
- install inside a python3 venv
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **deps:** update dependency adbario/php-dot-notation to v3 ([#2226](https://github.com/libretime/libretime/issues/2226))
|
|
||||||
- **deps:** update dependency league/uri to v6.7.2
|
|
||||||
- **legacy:** set platform requirements to php ^7.4
|
|
||||||
- **playout:** remove outdated liquidsoap code
|
|
||||||
- **playout:** add types
|
|
||||||
- **api:** allow single digit version for legacy schema
|
|
||||||
- **deps:** update dependency friendsofphp/php-cs-fixer to <3.12.1
|
|
||||||
- remove systemd ProtectHome feature ([#2243](https://github.com/libretime/libretime/issues/2243))
|
|
||||||
- **deps:** update dependency friendsofphp/php-cs-fixer to <3.13.1 ([#2249](https://github.com/libretime/libretime/issues/2249))
|
|
||||||
- **worker:** replace deprecated cgi.parse_header
|
|
||||||
- **installer:** install missing sudo
|
|
||||||
- **installer:** set home and login when running as postgres
|
|
||||||
- **legacy:** add log entry on task run ([#2316](https://github.com/libretime/libretime/issues/2316))
|
|
||||||
- **legacy:** log errors on connect check failure ([#2317](https://github.com/libretime/libretime/issues/2317))
|
|
||||||
- **deps:** update dependency friendsofphp/php-cs-fixer to <3.13.2
|
|
||||||
- **deps:** update dependency friendsofphp/php-cs-fixer to <3.13.3
|
|
||||||
- **legacy:** advanced search by track type id
|
|
||||||
- **legacy:** move forked deps to the libretime namespace
|
|
||||||
- **deps:** update dependency friendsofphp/php-cs-fixer to <3.14.4
|
|
||||||
- **deps:** update dependency friendsofphp/php-cs-fixer to <3.14.5
|
|
||||||
- **legacy:** ensure options is a dict during json encoding
|
|
||||||
- **legacy:** don't use dict assignment on object ([#2384](https://github.com/libretime/libretime/issues/2384))
|
|
||||||
- **playout:** quote escape strings in liquidsoap entrypoint
|
|
||||||
- **legacy:** do not delete audio file when removing artwork ([#2395](https://github.com/libretime/libretime/issues/2395))
|
|
||||||
- **playout:** use explicit ids for liquidsoap components
|
|
||||||
- **playout:** skip the identified queue instead of the current
|
|
||||||
- **playout:** use the same number of schedule queues
|
|
||||||
- **legacy:** on air light fails when no shows are scheduled
|
|
||||||
- **playout:** flush liquidsoap response before sending new
|
|
||||||
- **playout:** use package loader for liquidsoap templates
|
|
||||||
- **playout:** %else is not defined
|
|
||||||
- **playout:** when shows ends, next shows starts without fade-in/fade-out ([#2412](https://github.com/libretime/libretime/issues/2412))
|
|
||||||
- **playout:** legacy pushes non validated data
|
|
||||||
- **playout:** explicit ogg vorbis icecast encoder
|
|
||||||
- **playout:** prevent unbound variables
|
|
||||||
- **playout:** use int for liquidsoap queues map
|
|
||||||
- **shared:** return type confusion
|
|
||||||
- **deps:** update dependency friendsofphp/php-cs-fixer to <3.15.2
|
|
||||||
- **api:** explicit FileImportStatusEnum in schema
|
|
||||||
- pin postgresql version in docker-compose
|
|
||||||
- pin rabbitmq version in docker-compose
|
|
||||||
- allow overriding docker-compose predefined environment
|
|
||||||
- move docker specific setup to dockerfile
|
|
||||||
- **api:** cast string value to int enum ([#2461](https://github.com/libretime/libretime/issues/2461))
|
|
||||||
- **playout:** quote incompatible <py3.9 type hints
|
|
||||||
- **installer:** bump setuptools to ~=67.3 ([#2387](https://github.com/libretime/libretime/issues/2387))
|
|
||||||
- **playout:** use new api-client v1
|
|
||||||
- **playout:** catch oserror in liquidsoap client
|
|
||||||
- **deps:** update dependency friendsofphp/php-cs-fixer to <3.16.1 (main) ([#2490](https://github.com/libretime/libretime/issues/2490))
|
|
||||||
- **api:** require django >=4.2.0,<4.3
|
|
||||||
- **api:** upgrade psycopg to v3.1
|
|
||||||
- **playout:** remove unused ecasound package ([#2496](https://github.com/libretime/libretime/issues/2496))
|
|
||||||
- **installer:** ignore whitespace during diff
|
|
||||||
- **legacy:** don't print track_type id in show builder table ([#2510](https://github.com/libretime/libretime/issues/2510))
|
|
||||||
- **legacy:** remove composer superuser warning ([#2515](https://github.com/libretime/libretime/issues/2515))
|
|
||||||
- **legacy:** keep datatable settings between views ([#2519](https://github.com/libretime/libretime/issues/2519))
|
|
||||||
- **api:** upgrade django code (pre-commit)
|
|
||||||
- **analyzer:** remove unused python3 package
|
|
||||||
- **deps:** update dependency friendsofphp/php-cs-fixer to <3.17.1 (main) ([#2556](https://github.com/libretime/libretime/issues/2556))
|
|
||||||
|
|
||||||
### Documentation
|
|
||||||
|
|
||||||
- **playout:** add simple inputs pipeline schema ([#2240](https://github.com/libretime/libretime/issues/2240))
|
|
||||||
- add DOCKER_BUILDKIT env variable for docker-compose v1 ([#2270](https://github.com/libretime/libretime/issues/2270))
|
|
||||||
- no need to update release note path
|
|
||||||
- adapt c4 to our workflows
|
|
||||||
- stop providing maintenance releases for old distributions
|
|
||||||
- add pulseaudio output in containers tutorial ([#2166](https://github.com/libretime/libretime/issues/2166))
|
|
||||||
- remove warning about docker install ([#2411](https://github.com/libretime/libretime/issues/2411))
|
|
||||||
- docker-compose env variables setup
|
|
||||||
- add instructions for the sentry setup ([#2441](https://github.com/libretime/libretime/issues/2441))
|
|
||||||
- upgrade by migrating to a new server
|
|
||||||
- fix database backup and restore commands
|
|
||||||
- move contributing to docs/contribute
|
|
||||||
- split developer and contributor manual
|
|
||||||
- extract dev workflows from contributing docs
|
|
||||||
- add some history notes
|
|
||||||
- move release docs in the release section
|
|
||||||
- fix broken links
|
|
||||||
- ignore range format during docs linting
|
|
||||||
- only use microsoft styling guide
|
|
||||||
- move configuration documentation
|
|
||||||
- rename setup to install
|
|
||||||
- split install guide per install method
|
|
||||||
- docker config template install with envsubst ([#2517](https://github.com/libretime/libretime/issues/2517))
|
|
||||||
- improve reverse proxy docs
|
|
||||||
- improve install guides
|
|
||||||
- add certbot setup guide
|
|
||||||
- ensure example values are replaced
|
|
||||||
- fix broken link ([#2532](https://github.com/libretime/libretime/issues/2532))
|
|
||||||
- add note about unused packages
|
|
||||||
- improve airtime migration guide ([#2564](https://github.com/libretime/libretime/issues/2564))
|
|
||||||
- split airtime migration into more steps ([#2565](https://github.com/libretime/libretime/issues/2565))
|
|
||||||
- remove setup without reverse proxy
|
|
||||||
- fix icecast certificates bundle command
|
|
||||||
- install using a reverse proxy by default
|
|
||||||
- be consistent with example domain ([#2568](https://github.com/libretime/libretime/issues/2568))
|
|
||||||
- add 3.1.x distribution releases support
|
|
||||||
|
|
||||||
### Tests
|
|
||||||
|
|
||||||
- liquidsoap package from ppa is version 1.4.2 ([#2223](https://github.com/libretime/libretime/issues/2223))
|
|
||||||
- **playout:** refresh snapshots after major upgrade
|
|
||||||
- re-enable pylint logging-fstring-interpolation
|
|
||||||
- **playout:** more entrypoint config test cases
|
|
||||||
- **playout:** generated liquidsoap script syntax
|
|
||||||
- **playout:** silence existing broad-exception-caught errors
|
|
||||||
- **playout:** allow pylint failure
|
|
||||||
- **playout:** check untyped defs with mypy
|
|
||||||
- **api:** fix linting errors
|
|
||||||
- **shared:** fix linting errors
|
|
||||||
- **api:** fix linting errors
|
|
||||||
- **shared:** fix linting errors
|
|
||||||
- **playout:** class creation
|
|
||||||
- **api-client:** allow linters failure
|
|
||||||
- **playout:** move liq_conn fixture to conftest
|
|
||||||
- **playout:** liquidsoap wait for version
|
|
||||||
- **api:** add django-upgrade pre-commit hook
|
|
||||||
|
|
||||||
### CI
|
|
||||||
|
|
||||||
- test project weekly
|
|
||||||
- enable renovate for 3.0.x ([#2277](https://github.com/libretime/libretime/issues/2277))
|
|
||||||
- sync docs with libretime/website repository
|
|
||||||
- pin vale version to v2.21.3
|
|
||||||
- don't squash commits during docs sync
|
|
||||||
- always print diff when schema changes
|
|
||||||
- check if locale are up to date
|
|
||||||
- update locales weekly, not for every commit ([#2403](https://github.com/libretime/libretime/issues/2403))
|
|
||||||
- use bake file for container build
|
|
||||||
- allow manual ci trigger
|
|
||||||
- use bot to update locales
|
|
||||||
- replace deprecated set-output ([#2408](https://github.com/libretime/libretime/issues/2408))
|
|
||||||
- update docker hub containers description
|
|
||||||
- replace stale bot with stale action ([#2421](https://github.com/libretime/libretime/issues/2421))
|
|
||||||
- allow Falso as a word in codespell
|
|
||||||
- allow Falso as a word in codespell
|
|
||||||
- run all tests on python tools changes
|
|
||||||
- don't run stale bot on feature requests ([#2527](https://github.com/libretime/libretime/issues/2527))
|
|
||||||
|
|
||||||
### Reverts
|
|
||||||
|
|
||||||
- chore(api): install django-rest-framework from git ([#2518](https://github.com/libretime/libretime/issues/2518))
|
|
||||||
|
|
||||||
## [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/)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **legacy:** advanced search by track type id
|
|
||||||
- **legacy:** refresh lock files
|
|
||||||
- **legacy:** move forked deps to the libretime namespace
|
|
||||||
- **legacy:** improve error messages and logs
|
|
||||||
- **installer:** allow different actions on template_file
|
|
||||||
- **installer:** print diff on file deployment
|
|
||||||
- **installer:** only setup nginx on first install
|
|
||||||
- **installer:** print unsupported distribution error ([#2368](https://github.com/libretime/libretime/issues/2368))
|
|
||||||
- **installer:** create systemd dirs if missing ([#2379](https://github.com/libretime/libretime/issues/2379))
|
|
||||||
|
|
||||||
### Documentation
|
|
||||||
|
|
||||||
- add DOCKER_BUILDKIT env variable for docker-compose v1 ([#2270](https://github.com/libretime/libretime/issues/2270))
|
|
||||||
- check logs before checking services status
|
|
||||||
- add small faq for troubleshooting
|
|
||||||
|
|
||||||
### Tests
|
|
||||||
|
|
||||||
- **playout:** refresh snapshots after major upgrade ([#2381](https://github.com/libretime/libretime/issues/2381))
|
|
||||||
|
|
||||||
### CI
|
|
||||||
|
|
||||||
- don't squash commits during docs sync
|
|
||||||
- test project weekly
|
|
||||||
|
|
||||||
## [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/)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- remove systemd ProtectHome feature ([#2244](https://github.com/libretime/libretime/issues/2244))
|
|
||||||
- **installer:** install missing sudo
|
|
||||||
- **installer:** set home and login when running as postgres
|
|
||||||
- **legacy:** add log entry on task run ([#2316](https://github.com/libretime/libretime/issues/2316))
|
|
||||||
- **legacy:** log errors on connect check failure ([#2317](https://github.com/libretime/libretime/issues/2317))
|
|
||||||
- **worker:** replace deprecated cgi.parse_header
|
|
||||||
|
|
||||||
### Documentation
|
|
||||||
|
|
||||||
- no need to update release note path
|
|
||||||
|
|
||||||
### Tests
|
|
||||||
|
|
||||||
- liquidsoap package from ppa is version 1.4.2 ([#2233](https://github.com/libretime/libretime/issues/2233))
|
|
||||||
|
|
||||||
### CI
|
|
||||||
|
|
||||||
- run tests on 3.0.x
|
|
||||||
- enable renovate bot on 3.0.x
|
|
||||||
- sync docs with libretime/website repository
|
|
||||||
- pin vale version to v2.21.3
|
|
||||||
|
|
||||||
## [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/)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- clean exit by catching keyboard interrupt ([#2206](https://github.com/libretime/libretime/issues/2206))
|
|
||||||
- **legacy:** missing plupload uk_UA translation
|
|
||||||
- **legacy:** jquery i18n translations for plupload
|
|
||||||
- **legacy:** gracefully handle missing asset checksum
|
|
||||||
- disable some systemd security features on bionic ([#2219](https://github.com/libretime/libretime/issues/2219))
|
|
||||||
|
|
||||||
### Documentation
|
|
||||||
|
|
||||||
- **legacy:** how to add a new language
|
|
||||||
|
|
||||||
### Tests
|
|
||||||
|
|
||||||
- **analyzer:** fix wrong bit_rate values
|
|
||||||
|
|
||||||
## [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/)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- systemd service hardening ([#2186](https://github.com/libretime/libretime/issues/2186))
|
|
||||||
- extra systemd service hardening ([#2197](https://github.com/libretime/libretime/issues/2197))
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- start playout service after liquidsoap ([#2164](https://github.com/libretime/libretime/issues/2164))
|
|
||||||
- include version variable inside containers
|
|
||||||
- change version format
|
|
||||||
- **legacy:** add play button to stream player ([#2190](https://github.com/libretime/libretime/issues/2190))
|
|
||||||
- **legacy:** correct log levels ([#2196](https://github.com/libretime/libretime/issues/2196))
|
|
||||||
|
|
||||||
### Documentation
|
|
||||||
|
|
||||||
- remove breaking change warning ([#2180](https://github.com/libretime/libretime/issues/2180))
|
|
||||||
- fix vale linting errors
|
|
||||||
- fix vale linting error
|
|
||||||
|
|
||||||
### CI
|
|
||||||
|
|
||||||
- allow failure when linting /docs/releases
|
|
||||||
- use github.ref_name to get tag
|
|
||||||
|
|
||||||
## [3.0.0-beta.1](https://github.com/libretime/libretime/compare/3.0.0-beta.0...3.0.0-beta.1) (2022-09-23)
|
## [3.0.0-beta.1](https://github.com/libretime/libretime/compare/3.0.0-beta.0...3.0.0-beta.1) (2022-09-23)
|
||||||
|
|
||||||
|
@ -465,6 +32,8 @@
|
||||||
- don't check github.com/libretime/libretime/(issues|pulls) links
|
- don't check github.com/libretime/libretime/(issues|pulls) links
|
||||||
- run docs workflow on vale files changes
|
- 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)
|
## [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/)
|
- [Release note](https://libretime.org/docs/releases/3.0.0-beta.0/)
|
||||||
|
@ -614,6 +183,8 @@
|
||||||
- improve containers build caching
|
- improve containers build caching
|
||||||
- add container tags
|
- 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)
|
## [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/)
|
- [Release note](https://libretime.org/docs/releases/3.0.0-alpha.13/)
|
||||||
|
@ -774,6 +345,8 @@
|
||||||
- disable codecov project status check
|
- disable codecov project status check
|
||||||
- disable codecov patch 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)
|
## [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/)
|
- [Release note](https://libretime.org/docs/releases/3.0.0-alpha.12/)
|
||||||
|
@ -788,6 +361,8 @@
|
||||||
- add missing data to release note
|
- add missing data to release note
|
||||||
- fix and update links ([#1714](https://github.com/libretime/libretime/issues/1714))
|
- 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)
|
## [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/)
|
- [Release note](https://libretime.org/docs/releases/3.0.0-alpha.11/)
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
docs/contribute.md
|
|
|
@ -0,0 +1,129 @@
|
||||||
|
# Contributing to LibreTime
|
||||||
|
|
||||||
|
First and foremost, thank you! We appreciate that you want to contribute to
|
||||||
|
LibreTime, your time is valuable, and your contributions mean a lot to us.
|
||||||
|
|
||||||
|
Before any contribution, read and be prepared to adhere to our
|
||||||
|
[code of conduct](https://github.com/libretime/organization/blob/main/CODE_OF_CONDUCT.md).
|
||||||
|
|
||||||
|
In addition, LibreTime follow the standardized
|
||||||
|
[C4 development process](https://rfc.zeromq.org/spec:42/c4/), in which you can
|
||||||
|
find explanation about most of the development workflows for LibreTime.
|
||||||
|
|
||||||
|
**How to contribute**
|
||||||
|
|
||||||
|
- [Reporting bugs](#reporting-bugs)
|
||||||
|
- [Suggesting enhancements](#suggesting-enhancements)
|
||||||
|
- [Financially](https://libretime.org/contribute#financial)
|
||||||
|
- [Contributing to documentation](https://libretime.org/contribute#write-documentation)
|
||||||
|
- [Contributing to code](#code)
|
||||||
|
|
||||||
|
## Reporting bugs
|
||||||
|
|
||||||
|
This section guides you through submitting a bug report for LibreTime.
|
||||||
|
Following these guidelines helps maintainers and the community understand your
|
||||||
|
report, reproduce the behavior, and find related reports.
|
||||||
|
|
||||||
|
Before creating bug reports, please check the following list, to be sure that
|
||||||
|
you need to create one:
|
||||||
|
|
||||||
|
- **Check the [LibreTime forum](https://discourse.libretime.org/)** for existing
|
||||||
|
questions and discussion.
|
||||||
|
- **Check that your issue does not already exist in the
|
||||||
|
[issue tracker](https://github.com/libretime/libretime/issues?q=is%3aissue+label%3abug)**.
|
||||||
|
|
||||||
|
> **Note:** If you find a **Closed** issue that seems like it is the same thing
|
||||||
|
> that you're experiencing, open a new issue and include a link to the original
|
||||||
|
> issue in the body of your new one.
|
||||||
|
|
||||||
|
When you are creating a bug report, please include as many details as possible.
|
||||||
|
Fill out the [required template](https://github.com/libretime/libretime/issues/new?labels=bug&template=bug_report.md),
|
||||||
|
the information it asks helps the maintainers resolve the issue faster.
|
||||||
|
|
||||||
|
Bugs are tracked on the [official issue tracker](https://github.com/libretime/libretime/issues).
|
||||||
|
|
||||||
|
## Suggesting enhancements
|
||||||
|
|
||||||
|
This section guides you through submitting an enhancement suggestion for
|
||||||
|
LibreTime, including completely new features and minor improvements to existing
|
||||||
|
functionality. Following these guidelines helps maintainers and the community
|
||||||
|
understand your suggestion and find related suggestions.
|
||||||
|
|
||||||
|
Before creating enhancement suggestions, please check the following list, as you
|
||||||
|
might find out that you don't need to create one:
|
||||||
|
|
||||||
|
- **Check the [LibreTime forum](https://discourse.libretime.org/)** for existing
|
||||||
|
questions and discussion.
|
||||||
|
- **Check that your issue does not already exist in the
|
||||||
|
[issue tracker](https://github.com/libretime/libretime/issues?q=is%3aissue+label%3afeature-request)**.
|
||||||
|
|
||||||
|
When you are creating an enhancement suggestion, please include as many details
|
||||||
|
as possible. Fill in [the template](https://github.com/libretime/libretime/issues/new?labels=feature-request&template=feature_request.md),
|
||||||
|
including the steps that you imagine you would take if the feature you're
|
||||||
|
requesting existed.
|
||||||
|
|
||||||
|
## Code
|
||||||
|
|
||||||
|
Are you familiar with coding in PHP or Python? Have you made projects in
|
||||||
|
Liquidsoap and some of the other services we use? Take a look at the
|
||||||
|
[list of bugs and feature requests](https://github.com/libretime/libretime/issues),
|
||||||
|
and then fork our repo and have a go! Just use the **Fork** button at the top of
|
||||||
|
our [GitHub page](https://github.com/libretime/libretime), clone the forked repo
|
||||||
|
to your desktop, open up a favorite editor and make some changes, and then
|
||||||
|
commit, push, and open a pull request.
|
||||||
|
|
||||||
|
Knowledge on how to use [Github](https://guides.github.com/activities/hello-world/)
|
||||||
|
and [Git](https://git-scm.com/docs/gittutorial) will suit you well, use the
|
||||||
|
links for a quick 101.
|
||||||
|
|
||||||
|
LibreTime uses the [black](https://github.com/psf/black) coding style for Python
|
||||||
|
and you must ensure that your code follows it. If not, the CI will fail and your
|
||||||
|
Pull Request will not be merged. Similarly, the Python import statements are
|
||||||
|
sorted with [isort](https://github.com/pycqa/isort). There is configuration
|
||||||
|
provided for [pre-commit](https://pre-commit.com/), which will ensure that code
|
||||||
|
matches the expected style and conventions when you commit changes. It is set up
|
||||||
|
by running:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt install pre-commit
|
||||||
|
pre-commit install
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also run it anytime using:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pre-commit run --all-files
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing and CI/CD
|
||||||
|
|
||||||
|
Before submitting code to the project, it's a good idea to test it first. To do
|
||||||
|
this, it's easiest to install LibreTime in a virtual machine on your local
|
||||||
|
system or in a cloud VM. We have instructions for setting up a virtual instance
|
||||||
|
of LibreTime with [Vagrant](/docs/vagrant) and [Multipass](/docs/multipass).
|
||||||
|
|
||||||
|
If you would like to try LibreTime in a Docker image, Odclive has instructions
|
||||||
|
[here](https://github.com/kessibi/libretime-docker) for setting up a test image
|
||||||
|
and a more persistent install.
|
||||||
|
|
||||||
|
## Modifying the Database
|
||||||
|
|
||||||
|
LibreTime is designed to work with a [PostgreSQL](https://www.postgresql.org/)
|
||||||
|
database server running locally. LibreTime uses [PropelORM](https://github.com/propelorm/Propel)
|
||||||
|
to interact with the ZendPHP components and create the database. The version 2
|
||||||
|
API uses Django to interact with the same database.
|
||||||
|
|
||||||
|
If you are a developer seeking to add new columns to the database here are the steps.
|
||||||
|
|
||||||
|
1. Modify `legacy/build/schema.xml` with any changes.
|
||||||
|
2. Run `dev_tools/propel_generate.sh`
|
||||||
|
3. Update the upgrade.sql under `legacy/application/controllers/upgrade_sql/VERSION` for example
|
||||||
|
`ALTER TABLE imported_podcast ADD COLUMN album_override boolean default 'f' NOT NULL;`
|
||||||
|
4. Update the models under `api/libretime_api/models/` to reflect the new
|
||||||
|
changes.
|
||||||
|
|
||||||
|
## Documentation and financial contributions
|
||||||
|
|
||||||
|
More information about how to contribute documentation or financially
|
||||||
|
through our [OpenCollective](https://opencollective.com/libretime) can be found
|
||||||
|
on our [website](https://libretime.org/contribute).
|
92
Dockerfile
92
Dockerfile
|
@ -2,7 +2,7 @@ ARG LIBRETIME_VERSION
|
||||||
#======================================================================================#
|
#======================================================================================#
|
||||||
# Python Builder #
|
# Python Builder #
|
||||||
#======================================================================================#
|
#======================================================================================#
|
||||||
FROM python:3.10-slim-bullseye AS python-builder
|
FROM python:3.10-slim-bullseye as python-builder
|
||||||
|
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ RUN pip wheel --wheel-dir . --no-deps .
|
||||||
#======================================================================================#
|
#======================================================================================#
|
||||||
# Python base #
|
# Python base #
|
||||||
#======================================================================================#
|
#======================================================================================#
|
||||||
FROM python:3.10-slim-bullseye AS python-base
|
FROM python:3.10-slim-bullseye as python-base
|
||||||
|
|
||||||
ENV PYTHONDONTWRITEBYTECODE=1
|
ENV PYTHONDONTWRITEBYTECODE=1
|
||||||
ENV PYTHONUNBUFFERED=1
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
@ -28,9 +28,11 @@ ARG USER=libretime
|
||||||
ARG UID=1000
|
ARG UID=1000
|
||||||
ARG GID=1000
|
ARG GID=1000
|
||||||
|
|
||||||
RUN set -eux \
|
RUN adduser --disabled-password --uid=$UID --gecos '' --no-create-home ${USER}
|
||||||
&& adduser --disabled-password --uid=$UID --gecos '' --no-create-home ${USER} \
|
|
||||||
&& install --directory --owner=${USER} /etc/libretime /srv/libretime
|
RUN install --directory --owner=${USER} \
|
||||||
|
/etc/libretime \
|
||||||
|
/srv/libretime
|
||||||
|
|
||||||
ENV LIBRETIME_CONFIG_FILEPATH=/etc/libretime/config.yml
|
ENV LIBRETIME_CONFIG_FILEPATH=/etc/libretime/config.yml
|
||||||
|
|
||||||
|
@ -38,9 +40,8 @@ ENV LIBRETIME_CONFIG_FILEPATH=/etc/libretime/config.yml
|
||||||
COPY tools/packages.py /tmp/packages.py
|
COPY tools/packages.py /tmp/packages.py
|
||||||
COPY shared/packages.ini /tmp/packages.ini
|
COPY shared/packages.ini /tmp/packages.ini
|
||||||
|
|
||||||
RUN set -eux \
|
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
|
||||||
&& DEBIAN_FRONTEND=noninteractive apt-get update \
|
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||||
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
|
||||||
$(python3 /tmp/packages.py --format=line --exclude=python bullseye /tmp/packages.ini) \
|
$(python3 /tmp/packages.py --format=line --exclude=python bullseye /tmp/packages.ini) \
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
&& rm -f /tmp/packages.py /tmp/packages.ini
|
&& rm -f /tmp/packages.py /tmp/packages.ini
|
||||||
|
@ -48,25 +49,23 @@ RUN set -eux \
|
||||||
#======================================================================================#
|
#======================================================================================#
|
||||||
# Python base with ffmpeg #
|
# Python base with ffmpeg #
|
||||||
#======================================================================================#
|
#======================================================================================#
|
||||||
FROM python-base AS python-base-ffmpeg
|
FROM python-base as python-base-ffmpeg
|
||||||
|
|
||||||
RUN set -eux \
|
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
|
||||||
&& DEBIAN_FRONTEND=noninteractive apt-get update \
|
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||||
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
|
||||||
ffmpeg \
|
ffmpeg \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
#======================================================================================#
|
#======================================================================================#
|
||||||
# Analyzer #
|
# Analyzer #
|
||||||
#======================================================================================#
|
#======================================================================================#
|
||||||
FROM python-base-ffmpeg AS libretime-analyzer
|
FROM python-base-ffmpeg as libretime-analyzer
|
||||||
|
|
||||||
COPY tools/packages.py /tmp/packages.py
|
COPY tools/packages.py /tmp/packages.py
|
||||||
COPY analyzer/packages.ini /tmp/packages.ini
|
COPY analyzer/packages.ini /tmp/packages.ini
|
||||||
|
|
||||||
RUN set -eux \
|
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
|
||||||
&& DEBIAN_FRONTEND=noninteractive apt-get update \
|
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||||
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
|
||||||
$(python3 /tmp/packages.py --format=line --exclude=python bullseye /tmp/packages.ini) \
|
$(python3 /tmp/packages.py --format=line --exclude=python bullseye /tmp/packages.ini) \
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
&& rm -f /tmp/packages.py /tmp/packages.ini
|
&& rm -f /tmp/packages.py /tmp/packages.ini
|
||||||
|
@ -83,7 +82,7 @@ RUN --mount=type=cache,target=/root/.cache/pip \
|
||||||
|
|
||||||
COPY analyzer .
|
COPY analyzer .
|
||||||
RUN --mount=type=cache,target=/root/.cache/pip \
|
RUN --mount=type=cache,target=/root/.cache/pip \
|
||||||
pip install --editable .[sentry]
|
pip install --editable .
|
||||||
|
|
||||||
# Run
|
# Run
|
||||||
USER ${UID}:${GID}
|
USER ${UID}:${GID}
|
||||||
|
@ -97,14 +96,13 @@ ENV LIBRETIME_VERSION=$LIBRETIME_VERSION
|
||||||
#======================================================================================#
|
#======================================================================================#
|
||||||
# Playout #
|
# Playout #
|
||||||
#======================================================================================#
|
#======================================================================================#
|
||||||
FROM python-base-ffmpeg AS libretime-playout
|
FROM python-base-ffmpeg as libretime-playout
|
||||||
|
|
||||||
COPY tools/packages.py /tmp/packages.py
|
COPY tools/packages.py /tmp/packages.py
|
||||||
COPY playout/packages.ini /tmp/packages.ini
|
COPY playout/packages.ini /tmp/packages.ini
|
||||||
|
|
||||||
RUN set -eux \
|
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
|
||||||
&& DEBIAN_FRONTEND=noninteractive apt-get update \
|
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||||
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
|
||||||
$(python3 /tmp/packages.py --format=line --exclude=python bullseye /tmp/packages.ini) \
|
$(python3 /tmp/packages.py --format=line --exclude=python bullseye /tmp/packages.ini) \
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
&& rm -f /tmp/packages.py /tmp/packages.ini
|
&& rm -f /tmp/packages.py /tmp/packages.ini
|
||||||
|
@ -122,7 +120,7 @@ RUN --mount=type=cache,target=/root/.cache/pip \
|
||||||
|
|
||||||
COPY playout .
|
COPY playout .
|
||||||
RUN --mount=type=cache,target=/root/.cache/pip \
|
RUN --mount=type=cache,target=/root/.cache/pip \
|
||||||
pip install --editable .[sentry]
|
pip install --editable .
|
||||||
|
|
||||||
# Run
|
# Run
|
||||||
USER ${UID}:${GID}
|
USER ${UID}:${GID}
|
||||||
|
@ -136,12 +134,10 @@ ENV LIBRETIME_VERSION=$LIBRETIME_VERSION
|
||||||
#======================================================================================#
|
#======================================================================================#
|
||||||
# API #
|
# API #
|
||||||
#======================================================================================#
|
#======================================================================================#
|
||||||
FROM python-base AS libretime-api
|
FROM python-base as libretime-api
|
||||||
|
|
||||||
RUN set -eux \
|
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
|
||||||
&& DEBIAN_FRONTEND=noninteractive apt-get update \
|
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||||
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
|
||||||
curl \
|
|
||||||
gcc \
|
gcc \
|
||||||
libc6-dev \
|
libc6-dev \
|
||||||
libpq-dev \
|
libpq-dev \
|
||||||
|
@ -151,7 +147,7 @@ WORKDIR /src
|
||||||
|
|
||||||
COPY api/requirements.txt .
|
COPY api/requirements.txt .
|
||||||
RUN --mount=type=cache,target=/root/.cache/pip \
|
RUN --mount=type=cache,target=/root/.cache/pip \
|
||||||
pip install --no-compile -r requirements.txt
|
pip install --no-compile gunicorn uvicorn -r requirements.txt
|
||||||
|
|
||||||
COPY --from=python-builder /build/shared/*.whl .
|
COPY --from=python-builder /build/shared/*.whl .
|
||||||
RUN --mount=type=cache,target=/root/.cache/pip \
|
RUN --mount=type=cache,target=/root/.cache/pip \
|
||||||
|
@ -159,7 +155,7 @@ RUN --mount=type=cache,target=/root/.cache/pip \
|
||||||
|
|
||||||
COPY api .
|
COPY api .
|
||||||
RUN --mount=type=cache,target=/root/.cache/pip \
|
RUN --mount=type=cache,target=/root/.cache/pip \
|
||||||
pip install --editable .[prod,sentry]
|
pip install --editable .[prod]
|
||||||
|
|
||||||
# Run
|
# Run
|
||||||
USER ${UID}:${GID}
|
USER ${UID}:${GID}
|
||||||
|
@ -167,7 +163,7 @@ WORKDIR /app
|
||||||
|
|
||||||
CMD ["/usr/local/bin/gunicorn", \
|
CMD ["/usr/local/bin/gunicorn", \
|
||||||
"--workers=4", \
|
"--workers=4", \
|
||||||
"--worker-class=libretime_api.gunicorn.Worker", \
|
"--worker-class=uvicorn.workers.UvicornWorker", \
|
||||||
"--log-file", "-", \
|
"--log-file", "-", \
|
||||||
"--bind=0.0.0.0:9001", \
|
"--bind=0.0.0.0:9001", \
|
||||||
"libretime_api.asgi"]
|
"libretime_api.asgi"]
|
||||||
|
@ -175,12 +171,10 @@ CMD ["/usr/local/bin/gunicorn", \
|
||||||
ARG LIBRETIME_VERSION
|
ARG LIBRETIME_VERSION
|
||||||
ENV LIBRETIME_VERSION=$LIBRETIME_VERSION
|
ENV LIBRETIME_VERSION=$LIBRETIME_VERSION
|
||||||
|
|
||||||
HEALTHCHECK CMD ["curl", "--fail", "http://localhost:9001/api/v2/version"]
|
|
||||||
|
|
||||||
#======================================================================================#
|
#======================================================================================#
|
||||||
# Worker #
|
# Worker #
|
||||||
#======================================================================================#
|
#======================================================================================#
|
||||||
FROM python-base AS libretime-worker
|
FROM python-base as libretime-worker
|
||||||
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
|
@ -189,42 +183,47 @@ RUN --mount=type=cache,target=/root/.cache/pip \
|
||||||
pip install --no-compile -r requirements.txt
|
pip install --no-compile -r requirements.txt
|
||||||
|
|
||||||
COPY --from=python-builder /build/shared/*.whl .
|
COPY --from=python-builder /build/shared/*.whl .
|
||||||
COPY --from=python-builder /build/api-client/*.whl .
|
|
||||||
RUN --mount=type=cache,target=/root/.cache/pip \
|
RUN --mount=type=cache,target=/root/.cache/pip \
|
||||||
pip install --no-compile *.whl && rm -Rf *.whl
|
pip install --no-compile *.whl && rm -Rf *.whl
|
||||||
|
|
||||||
COPY worker .
|
COPY worker .
|
||||||
RUN --mount=type=cache,target=/root/.cache/pip \
|
RUN --mount=type=cache,target=/root/.cache/pip \
|
||||||
pip install --editable .[sentry]
|
pip install --editable .
|
||||||
|
|
||||||
# Run
|
# Run
|
||||||
USER ${UID}:${GID}
|
USER ${UID}:${GID}
|
||||||
WORKDIR /app
|
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
|
ARG LIBRETIME_VERSION
|
||||||
ENV LIBRETIME_VERSION=$LIBRETIME_VERSION
|
ENV LIBRETIME_VERSION=$LIBRETIME_VERSION
|
||||||
|
|
||||||
#======================================================================================#
|
#======================================================================================#
|
||||||
# Legacy #
|
# 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_CONFIG_FILEPATH=/etc/libretime/config.yml
|
||||||
ENV LIBRETIME_LOG_FILEPATH=php://stderr
|
|
||||||
|
|
||||||
# Custom user
|
# Custom user
|
||||||
ARG USER=libretime
|
ARG USER=libretime
|
||||||
ARG UID=1000
|
ARG UID=1000
|
||||||
ARG GID=1000
|
ARG GID=1000
|
||||||
|
|
||||||
RUN set -eux \
|
RUN adduser --disabled-password --uid=$UID --gecos '' --no-create-home ${USER}
|
||||||
&& adduser --disabled-password --uid=$UID --gecos '' --no-create-home ${USER} \
|
|
||||||
&& install --directory --owner=${USER} /etc/libretime /srv/libretime
|
|
||||||
|
|
||||||
RUN set -eux \
|
RUN install --directory --owner=${USER} \
|
||||||
&& DEBIAN_FRONTEND=noninteractive apt-get update \
|
/etc/libretime \
|
||||||
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
/srv/libretime
|
||||||
|
|
||||||
|
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||||
gettext \
|
gettext \
|
||||||
libcurl4-openssl-dev \
|
libcurl4-openssl-dev \
|
||||||
libfreetype6-dev \
|
libfreetype6-dev \
|
||||||
|
@ -269,9 +268,8 @@ COPY legacy/composer.* ./
|
||||||
RUN composer --no-cache install --no-progress --no-interaction --no-dev --no-autoloader
|
RUN composer --no-cache install --no-progress --no-interaction --no-dev --no-autoloader
|
||||||
|
|
||||||
COPY legacy .
|
COPY legacy .
|
||||||
RUN set -eux \
|
RUN make locale-build
|
||||||
&& make locale-build \
|
RUN composer --no-cache dump-autoload --no-interaction --no-dev
|
||||||
&& composer --no-cache dump-autoload --no-interaction --no-dev
|
|
||||||
|
|
||||||
# Run
|
# Run
|
||||||
USER ${UID}:${GID}
|
USER ${UID}:${GID}
|
||||||
|
|
62
Makefile
62
Makefile
|
@ -7,57 +7,36 @@ all: setup
|
||||||
setup:
|
setup:
|
||||||
command -v pre-commit > /dev/null && pre-commit install
|
command -v pre-commit > /dev/null && pre-commit install
|
||||||
|
|
||||||
.env:
|
# https://google.github.io/styleguide/shellguide.html
|
||||||
cp .env.dev .env
|
shell-format:
|
||||||
|
shfmt -f . | xargs git ls-files | xargs shfmt -i 2 -ci -sr -kp -w
|
||||||
|
|
||||||
dev-certs:
|
shell-check:
|
||||||
rm -f dev/certs/fake.*
|
shfmt -f . | xargs git ls-files | xargs shfmt -i 2 -ci -sr -kp -d
|
||||||
openssl req -x509 \
|
shfmt -f . | xargs git ls-files | xargs shellcheck --color=always --severity=$${SEVERITY:-style}
|
||||||
-newkey rsa:2048 \
|
|
||||||
-days 365 \
|
|
||||||
-nodes \
|
|
||||||
-subj "/CN=localhost" -addext "subjectAltName=DNS:localhost,IP:127.0.0.1" \
|
|
||||||
-keyout dev/certs/fake.key \
|
|
||||||
-out dev/certs/fake.crt
|
|
||||||
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
|
|
||||||
|
|
||||||
.PHONY: VERSION
|
.PHONY: VERSION
|
||||||
VERSION:
|
VERSION:
|
||||||
tools/version.sh
|
tools/version.sh
|
||||||
|
|
||||||
|
changelog:
|
||||||
|
tools/changelog.sh
|
||||||
|
|
||||||
.PHONY: tarball
|
.PHONY: tarball
|
||||||
tarball: VERSION
|
tarball: VERSION
|
||||||
$(MAKE) -C legacy build
|
$(MAKE) -C legacy build
|
||||||
cd .. && tar -czf libretime-$(shell cat VERSION | tr -d [:blank:]).tar.gz \
|
cd .. && tar -czf libretime-$(shell cat VERSION | tr -d [:blank:]).tar.gz \
|
||||||
--owner=root --group=root \
|
--owner=root --group=root \
|
||||||
--exclude-vcs \
|
--exclude-vcs \
|
||||||
libretime/analyzer \
|
--exclude .codespellignore \
|
||||||
libretime/api \
|
--exclude .git* \
|
||||||
libretime/api-client \
|
--exclude .pre-commit-config.yaml \
|
||||||
libretime/docs \
|
--exclude dev_tools \
|
||||||
libretime/installer \
|
--exclude jekyll.sh \
|
||||||
libretime/legacy \
|
|
||||||
--exclude legacy/vendor/phing \
|
--exclude legacy/vendor/phing \
|
||||||
--exclude legacy/vendor/simplepie/simplepie/tests \
|
--exclude legacy/vendor/simplepie/simplepie/tests \
|
||||||
libretime/playout \
|
libretime
|
||||||
libretime/shared \
|
|
||||||
libretime/tools \
|
|
||||||
libretime/worker \
|
|
||||||
libretime/CHANGELOG.md \
|
|
||||||
libretime/install \
|
|
||||||
libretime/LICENSE \
|
|
||||||
libretime/Makefile \
|
|
||||||
libretime/README.md \
|
|
||||||
libretime/SECURITY.md \
|
|
||||||
libretime/VERSION
|
|
||||||
mv ../libretime-*.tar.gz .
|
mv ../libretime-*.tar.gz .
|
||||||
sha256sum libretime-*.tar.gz > sha256sums.txt
|
|
||||||
|
|
||||||
# Only clean subdirs
|
# Only clean subdirs
|
||||||
clean:
|
clean:
|
||||||
|
@ -65,13 +44,4 @@ clean:
|
||||||
|
|
||||||
docs-lint:
|
docs-lint:
|
||||||
vale sync
|
vale sync
|
||||||
vale docs
|
vale docs website/src/pages
|
||||||
|
|
||||||
website:
|
|
||||||
git clone git@github.com:libretime/website.git
|
|
||||||
|
|
||||||
website/node_modules: website
|
|
||||||
yarn --cwd website install
|
|
||||||
|
|
||||||
docs-dev: website website/node_modules
|
|
||||||
DOCS_PATH="../docs" yarn --cwd website start
|
|
||||||
|
|
50
README.md
50
README.md
|
@ -1,4 +1,4 @@
|
||||||
# [](https://github.com/libretime/libretime)
|

|
||||||
|
|
||||||
[](https://opencollective.com/libretime)
|
[](https://opencollective.com/libretime)
|
||||||
|
|
||||||
|
@ -9,15 +9,33 @@ It is managed by a friendly inclusive community of stations from around the
|
||||||
globe that use, document and improve LibreTime. Join us in fixing bugs and in
|
globe that use, document and improve LibreTime. Join us in fixing bugs and in
|
||||||
defining how we manage the codebase going forward.
|
defining how we manage the codebase going forward.
|
||||||
|
|
||||||
Check out the [documentation](https://libretime.org/docs/) for more information and
|
We are currently ramping up development on this repository.
|
||||||
|
|
||||||
|
Check out the [documentation](https://libretime.org) for more information and
|
||||||
start broadcasting!
|
start broadcasting!
|
||||||
|
|
||||||
Please note that LibreTime is released with a [Contributor Code
|
Please note that LibreTime is released with a [Contributor Code
|
||||||
of Conduct](https://github.com/libretime/organization/blob/main/CODE_OF_CONDUCT.md).
|
of Conduct](https://github.com/libretime/organization/blob/main/CODE_OF_CONDUCT.md).
|
||||||
By participating in this project you agree to abide by its terms.
|
By participating in this project you agree to abide by its terms.
|
||||||
|
|
||||||
You can find details about our development process in the
|
Please submit enhancements, bug-fixes or comments via GitHub.
|
||||||
[contributing](./CONTRIBUTING.md) guide.
|
|
||||||
|
## Development Process
|
||||||
|
|
||||||
|
The LibreTime follows the standardized [Collective Code Construction
|
||||||
|
Contract (C4)](https://rfc.zeromq.org/spec:42/C4/). Its abstract is
|
||||||
|
provided here.
|
||||||
|
|
||||||
|
> C4 provides a standard process for contributing, evaluating and
|
||||||
|
> discussing improvements on software projects. It defines specific
|
||||||
|
> technical requirements for projects like a style guide, unit tests,
|
||||||
|
> git and similar platforms. It also establishes different personas
|
||||||
|
> for projects, with clear and distinct duties. C4 specifies a process
|
||||||
|
> for documenting and discussing issues including seeking consensus
|
||||||
|
> and clear descriptions, use of "pull requests" and systematic reviews.
|
||||||
|
|
||||||
|
The full text of the contract is licensed under the GPL and available at
|
||||||
|
the above link courtesy of the [ZeroMQ community](https://zeromq.org/).
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
|
@ -26,8 +44,7 @@ we have a forum at [discourse.libretime.org](https://discourse.libretime.org).
|
||||||
We are moving towards using the forum to provide community support and reserving
|
We are moving towards using the forum to provide community support and reserving
|
||||||
the github issue queue for confirmed bugs and well-formed feature requests.
|
the github issue queue for confirmed bugs and well-formed feature requests.
|
||||||
|
|
||||||
You can also contact us through [Matrix
|
You can also contact us through our [Mattermost instance](https://chat.libretime.org)
|
||||||
(#libretime:matrix.org)](https://matrix.to/#/#libretime:matrix.org)
|
|
||||||
where you can talk with other users and developers.
|
where you can talk with other users and developers.
|
||||||
|
|
||||||
## Contributors
|
## Contributors
|
||||||
|
@ -52,8 +69,23 @@ Become a financial contributor and help us sustain our community on
|
||||||
[Support](https://opencollective.com/libretime/contribute) this project with
|
[Support](https://opencollective.com/libretime/contribute) this project with
|
||||||
your organization. Your logo will show up here with a link to your website.
|
your organization. Your logo will show up here with a link to your website.
|
||||||
|
|
||||||
<a href="https://opencollective.com/libretime">
|
<a href="https://opencollective.com/libretime/organization/0/website">
|
||||||
<img src="https://opencollective.com/libretime/organizations.svg?width=890">
|
<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>
|
</a>
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
@ -67,6 +99,6 @@ version 3 of the License.
|
||||||
|
|
||||||
Copyright (c) 2011-2017 Sourcefabric z.ú.
|
Copyright (c) 2011-2017 Sourcefabric z.ú.
|
||||||
|
|
||||||
Copyright (c) 2017-2023 LibreTime Community
|
Copyright (c) 2017-2022 LibreTime Community
|
||||||
|
|
||||||
Please refer to the [LEGACY](./LEGACY.md) file for more information.
|
Please refer to the [LEGACY](./LEGACY.md) file for more information.
|
||||||
|
|
14
SECURITY.md
14
SECURITY.md
|
@ -1,14 +0,0 @@
|
||||||
# Security Policy
|
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
|
||||||
|
|
||||||
**Please do not use GitHub issues for security-sensitive communication.**
|
|
||||||
|
|
||||||
The LibreTime maintainers ask that known and suspected vulnerabilities to be privately and responsibly disclosed by:
|
|
||||||
|
|
||||||
- sending all the required detail to [security@libretime.org](security@libretime.org),
|
|
||||||
- or by filling a [security advisory on Github](https://github.com/libretime/libretime/security/advisories/new).
|
|
||||||
|
|
||||||
A LibreTime maintainer will acknowledged the report within 3 working days.
|
|
||||||
|
|
||||||
We aim to provide a security patch within 30 days, after this period the report will be disclosed to the public. The security patch will be distributed for the [maintained versions of LibreTime](https://libretime.org/docs/releases/#distributions-releases-support).
|
|
|
@ -6,7 +6,7 @@
|
||||||
# export VAGRANT_NO_PORT_FORWARDING=true
|
# export VAGRANT_NO_PORT_FORWARDING=true
|
||||||
# export VAGRANT_CPUS=4
|
# export VAGRANT_CPUS=4
|
||||||
# export VAGRANT_MEMORY=4096
|
# export VAGRANT_MEMORY=4096
|
||||||
# vagrant up bullseye
|
# vagrant up buster
|
||||||
#
|
#
|
||||||
|
|
||||||
Vagrant.configure('2') do |config|
|
Vagrant.configure('2') do |config|
|
||||||
|
@ -79,6 +79,7 @@ Vagrant.configure('2') do |config|
|
||||||
LIBRETIME_POSTGRESQL_PASSWORD=libretime \
|
LIBRETIME_POSTGRESQL_PASSWORD=libretime \
|
||||||
LIBRETIME_RABBITMQ_PASSWORD=libretime \
|
LIBRETIME_RABBITMQ_PASSWORD=libretime \
|
||||||
bash install \
|
bash install \
|
||||||
|
--listen-port 8080 \
|
||||||
--in-place \
|
--in-place \
|
||||||
http://192.168.10.100:8080
|
http://192.168.10.100:8080
|
||||||
|
|
||||||
|
@ -98,6 +99,12 @@ Vagrant.configure('2') do |config|
|
||||||
setup_libretime(os, "debian.sh")
|
setup_libretime(os, "debian.sh")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
config.vm.define 'bionic' do |os|
|
||||||
|
os.vm.box = 'bento/ubuntu-18.04'
|
||||||
|
setup_nfs(config)
|
||||||
|
setup_libretime(os, 'debian.sh')
|
||||||
|
end
|
||||||
|
|
||||||
config.vm.define 'bullseye' do |os|
|
config.vm.define 'bullseye' do |os|
|
||||||
os.vm.box = 'debian/bullseye64'
|
os.vm.box = 'debian/bullseye64'
|
||||||
config.vm.provider 'virtualbox' do |v, override|
|
config.vm.provider 'virtualbox' do |v, override|
|
||||||
|
@ -106,4 +113,19 @@ Vagrant.configure('2') do |config|
|
||||||
setup_nfs(config, 4)
|
setup_nfs(config, 4)
|
||||||
setup_libretime(os, 'debian.sh')
|
setup_libretime(os, 'debian.sh')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
config.vm.define 'buster' do |os|
|
||||||
|
os.vm.box = 'debian/buster64'
|
||||||
|
config.vm.provider 'virtualbox' do |v, override|
|
||||||
|
override.vm.box = 'bento/debian-10'
|
||||||
|
end
|
||||||
|
setup_nfs(config)
|
||||||
|
setup_libretime(os, 'debian.sh')
|
||||||
|
end
|
||||||
|
|
||||||
|
config.vm.define 'centos' do |os|
|
||||||
|
os.vm.box = 'centos/8'
|
||||||
|
setup_nfs(config)
|
||||||
|
setup_libretime(os, 'centos.sh', '--selinux')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,10 +4,11 @@ include ../tools/python.mk
|
||||||
|
|
||||||
PIP_INSTALL := \
|
PIP_INSTALL := \
|
||||||
--editable ../shared \
|
--editable ../shared \
|
||||||
--editable .[dev,sentry]
|
--editable .[dev]
|
||||||
PYLINT_ARG := libretime_analyzer tests || true
|
PYLINT_ARG := libretime_analyzer tests || true
|
||||||
MYPY_ARG := libretime_analyzer tests || true
|
MYPY_ARG := libretime_analyzer tests || true
|
||||||
BANDIT_ARG := libretime_analyzer || true
|
BANDIT_ARG := libretime_analyzer || true
|
||||||
|
PYTEST_ARG := --cov=libretime_analyzer tests
|
||||||
|
|
||||||
format: .format
|
format: .format
|
||||||
lint: .format-check .pylint .mypy .bandit
|
lint: .format-check .pylint .mypy .bandit
|
||||||
|
@ -16,5 +17,4 @@ fixtures:
|
||||||
bash tests/fixtures/generate.sh
|
bash tests/fixtures/generate.sh
|
||||||
|
|
||||||
test: fixtures .pytest
|
test: fixtures .pytest
|
||||||
test-coverage: fixtures .coverage
|
|
||||||
clean: .clean
|
clean: .clean
|
||||||
|
|
|
@ -10,6 +10,7 @@ PrivateTmp=true
|
||||||
PrivateUsers=true
|
PrivateUsers=true
|
||||||
ProtectClock=true
|
ProtectClock=true
|
||||||
ProtectControlGroups=true
|
ProtectControlGroups=true
|
||||||
|
ProtectHome=true
|
||||||
ProtectHostname=true
|
ProtectHostname=true
|
||||||
ProtectKernelLogs=true
|
ProtectKernelLogs=true
|
||||||
ProtectKernelModules=true
|
ProtectKernelModules=true
|
||||||
|
@ -17,7 +18,6 @@ ProtectKernelTunables=true
|
||||||
ProtectProc=invisible
|
ProtectProc=invisible
|
||||||
ProtectSystem=full
|
ProtectSystem=full
|
||||||
|
|
||||||
Environment=PYTHONOPTIMIZE=2
|
|
||||||
Environment=LIBRETIME_CONFIG_FILEPATH=@@CONFIG_FILEPATH@@
|
Environment=LIBRETIME_CONFIG_FILEPATH=@@CONFIG_FILEPATH@@
|
||||||
Environment=LIBRETIME_LOG_FILEPATH=@@LOG_DIR@@/analyzer.log
|
Environment=LIBRETIME_LOG_FILEPATH=@@LOG_DIR@@/analyzer.log
|
||||||
WorkingDirectory=@@WORKING_DIR@@/analyzer
|
WorkingDirectory=@@WORKING_DIR@@/analyzer
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
from importlib.metadata import version as get_version
|
|
||||||
|
|
||||||
PACKAGE = __name__
|
|
||||||
VERSION = get_version(__name__)
|
|
|
@ -1,20 +1,15 @@
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import click
|
import click
|
||||||
from libretime_shared.cli import cli_config_options, cli_logging_options
|
from libretime_shared.cli import cli_config_options, cli_logging_options
|
||||||
from libretime_shared.config import DEFAULT_ENV_PREFIX
|
from libretime_shared.config import DEFAULT_ENV_PREFIX
|
||||||
from libretime_shared.logging import setup_logger
|
from libretime_shared.logging import level_from_name, setup_logger
|
||||||
|
|
||||||
from . import PACKAGE, VERSION
|
|
||||||
from .config import Config
|
from .config import Config
|
||||||
from .message_listener import MessageListener
|
from .message_listener import MessageListener
|
||||||
from .status_reporter import StatusReporter
|
from .status_reporter import StatusReporter
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
VERSION = "1.0"
|
VERSION = "1.0"
|
||||||
|
|
||||||
DEFAULT_RETRY_QUEUE_FILEPATH = Path("retry_queue")
|
DEFAULT_RETRY_QUEUE_FILEPATH = Path("retry_queue")
|
||||||
|
@ -38,19 +33,9 @@ def cli(
|
||||||
"""
|
"""
|
||||||
Run analyzer.
|
Run analyzer.
|
||||||
"""
|
"""
|
||||||
setup_logger(log_level, log_filepath)
|
setup_logger(level_from_name(log_level), log_filepath)
|
||||||
config = Config(config_filepath)
|
config = Config(config_filepath)
|
||||||
|
|
||||||
if "SENTRY_DSN" in os.environ:
|
|
||||||
logger.info("installing sentry")
|
|
||||||
# pylint: disable=import-outside-toplevel
|
|
||||||
import sentry_sdk
|
|
||||||
|
|
||||||
sentry_sdk.init(
|
|
||||||
traces_sample_rate=1.0,
|
|
||||||
release=f"{PACKAGE}@{VERSION}",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Start up the StatusReporter process
|
# Start up the StatusReporter process
|
||||||
StatusReporter.start_thread(retry_queue_filepath)
|
StatusReporter.start_thread(retry_queue_filepath)
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
import json
|
import json
|
||||||
import logging
|
|
||||||
import signal
|
import signal
|
||||||
import time
|
import time
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
|
|
||||||
import pika
|
import pika
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
from .config import Config
|
from .config import Config
|
||||||
from .pipeline import Pipeline, PipelineOptions, PipelineStatus
|
from .pipeline import Pipeline, PipelineStatus
|
||||||
from .status_reporter import StatusReporter
|
from .status_reporter import StatusReporter
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
EXCHANGE = "airtime-uploads"
|
EXCHANGE = "airtime-uploads"
|
||||||
EXCHANGE_TYPE = "topic"
|
EXCHANGE_TYPE = "topic"
|
||||||
ROUTING_KEY = ""
|
ROUTING_KEY = ""
|
||||||
|
@ -100,7 +98,7 @@ class MessageListener:
|
||||||
Here we parse the message, spin up an analyzer process, and report the
|
Here we parse the message, spin up an analyzer process, and report the
|
||||||
metadata back to the Airtime web application (or report an error).
|
metadata back to the Airtime web application (or report an error).
|
||||||
"""
|
"""
|
||||||
logger.info("Received '%s' on routing_key '%s'", body, method_frame.routing_key)
|
logger.info(f" - Received '{body}' on routing_key '{method_frame.routing_key}'")
|
||||||
|
|
||||||
audio_file_path = ""
|
audio_file_path = ""
|
||||||
# final_file_path = ""
|
# final_file_path = ""
|
||||||
|
@ -113,19 +111,17 @@ class MessageListener:
|
||||||
body = body.decode()
|
body = body.decode()
|
||||||
except (UnicodeDecodeError, AttributeError):
|
except (UnicodeDecodeError, AttributeError):
|
||||||
pass
|
pass
|
||||||
msg_dict: dict = json.loads(body)
|
msg_dict = json.loads(body)
|
||||||
|
|
||||||
file_id = msg_dict["file_id"]
|
file_id = msg_dict["file_id"]
|
||||||
audio_file_path = msg_dict["tmp_file_path"]
|
audio_file_path = msg_dict["tmp_file_path"]
|
||||||
original_filename = msg_dict["original_filename"]
|
original_filename = msg_dict["original_filename"]
|
||||||
import_directory = msg_dict["import_directory"]
|
import_directory = msg_dict["import_directory"]
|
||||||
options = msg_dict.get("options", {})
|
|
||||||
|
|
||||||
metadata = MessageListener.spawn_analyzer_process(
|
metadata = MessageListener.spawn_analyzer_process(
|
||||||
audio_file_path,
|
audio_file_path,
|
||||||
import_directory,
|
import_directory,
|
||||||
original_filename,
|
original_filename,
|
||||||
options,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
callback_url = f"{self.config.general.public_url}/rest/media/{file_id}"
|
callback_url = f"{self.config.general.public_url}/rest/media/{file_id}"
|
||||||
|
@ -165,7 +161,6 @@ class MessageListener:
|
||||||
audio_file_path,
|
audio_file_path,
|
||||||
import_directory,
|
import_directory,
|
||||||
original_filename,
|
original_filename,
|
||||||
options: dict,
|
|
||||||
):
|
):
|
||||||
metadata = {}
|
metadata = {}
|
||||||
|
|
||||||
|
@ -176,11 +171,10 @@ class MessageListener:
|
||||||
audio_file_path,
|
audio_file_path,
|
||||||
import_directory,
|
import_directory,
|
||||||
original_filename,
|
original_filename,
|
||||||
PipelineOptions(**options),
|
|
||||||
)
|
)
|
||||||
metadata = queue.get()
|
metadata = queue.get()
|
||||||
except Exception as exception:
|
except Exception as exception:
|
||||||
logger.exception("Analyzer pipeline exception: %s", exception)
|
logger.exception(f"Analyzer pipeline exception: {exception}")
|
||||||
metadata["import_status"] = PipelineStatus.FAILED
|
metadata["import_status"] = PipelineStatus.FAILED
|
||||||
|
|
||||||
# Ensure our queue doesn't fill up and block due to unexpected behavior. Defensive code.
|
# Ensure our queue doesn't fill up and block due to unexpected behavior. Defensive code.
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
from .pipeline import Pipeline, PipelineOptions, PipelineStatus
|
from .pipeline import Pipeline, PipelineStatus
|
||||||
|
|
|
@ -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.
|
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)
|
track_gain_match = _PROBE_REPLAYGAIN_RE.search(cmd.stderr)
|
||||||
|
|
||||||
|
@ -75,7 +75,8 @@ def compute_silences(filepath: Path) -> List[Tuple[float, float]]:
|
||||||
cmd = _ffmpeg(
|
cmd = _ffmpeg(
|
||||||
*("-i", filepath),
|
*("-i", filepath),
|
||||||
"-vn",
|
"-vn",
|
||||||
*("-filter", "highpass=frequency=80,silencedetect=noise=-60dB:duration=0.9"),
|
*("-filter", "highpass=frequency=1000"),
|
||||||
|
*("-filter", "silencedetect=noise=0.15:duration=1"),
|
||||||
)
|
)
|
||||||
|
|
||||||
starts, ends = [], []
|
starts, ends = [], []
|
||||||
|
@ -93,8 +94,9 @@ def compute_silences(filepath: Path) -> List[Tuple[float, float]]:
|
||||||
end = float(match.group(2))
|
end = float(match.group(2))
|
||||||
ends.append(end)
|
ends.append(end)
|
||||||
|
|
||||||
# If one end is missing, set the last silence ending to infinity, and
|
# ffmpeg v3 (bionic) does not warn about silence end when the track ends.
|
||||||
# clamp it to the track duration before using this value.
|
# Set the last silence ending to infinity, and clamp it to the track duration before
|
||||||
|
# using this value.
|
||||||
if len(starts) - 1 == len(ends):
|
if len(starts) - 1 == len(ends):
|
||||||
ends.append(inf)
|
ends.append(inf)
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import logging
|
from subprocess import PIPE, CalledProcessError, CompletedProcess, run
|
||||||
from subprocess import CalledProcessError, CompletedProcess, run
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
from loguru import logger
|
||||||
|
|
||||||
|
|
||||||
def run_(*args, **kwargs) -> CompletedProcess:
|
def run_(*args, **kwargs) -> CompletedProcess:
|
||||||
|
@ -9,14 +8,15 @@ def run_(*args, **kwargs) -> CompletedProcess:
|
||||||
return run(
|
return run(
|
||||||
args,
|
args,
|
||||||
check=True,
|
check=True,
|
||||||
capture_output=True,
|
stdout=PIPE,
|
||||||
text=True,
|
stderr=PIPE,
|
||||||
|
universal_newlines=True,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
|
||||||
except OSError as exception: # executable was not found
|
except OSError as exception: # executable was not found
|
||||||
cmd = args[0]
|
cmd = args[0]
|
||||||
logger.warning("Failed to run: %s - %s. Is %s installed?", cmd, exception, cmd)
|
logger.warning(f"Failed to run: {cmd} - {exception}. Is {cmd} installed?")
|
||||||
raise exception
|
raise exception
|
||||||
|
|
||||||
except CalledProcessError as exception: # returned an error code
|
except CalledProcessError as exception: # returned an error code
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
import logging
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from math import isclose
|
from math import isclose
|
||||||
from subprocess import CalledProcessError
|
from subprocess import CalledProcessError
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
from ._ffmpeg import compute_silences, probe_duration
|
from ._ffmpeg import compute_silences, probe_duration
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
def analyze_cuepoint(filepath: str, metadata: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Extracts the cuein and cueout times along and sets the file duration using ffmpeg.
|
||||||
|
"""
|
||||||
|
|
||||||
def analyze_duration(filepath: str, metadata: Dict[str, Any]) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Extracts the file duration using ffmpeg.
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
duration = probe_duration(filepath)
|
duration = probe_duration(filepath)
|
||||||
|
|
||||||
|
@ -30,23 +30,7 @@ def analyze_duration(filepath: str, metadata: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
metadata["length"] = str(timedelta(seconds=duration))
|
metadata["length"] = str(timedelta(seconds=duration))
|
||||||
metadata["cuein"] = 0.0
|
metadata["cuein"] = 0.0
|
||||||
metadata["cueout"] = duration
|
metadata["cueout"] = duration
|
||||||
except (CalledProcessError, OSError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
return metadata
|
|
||||||
|
|
||||||
|
|
||||||
def analyze_cuepoint(filepath: str, metadata: Dict[str, Any]) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Extracts the cuein and cueout times using ffmpeg.
|
|
||||||
|
|
||||||
This step must run after the 'analyze_duration' step.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Duration has been computed in the 'analyze_duration' step
|
|
||||||
duration = metadata["length_seconds"]
|
|
||||||
|
|
||||||
try:
|
|
||||||
silences = compute_silences(filepath)
|
silences = compute_silences(filepath)
|
||||||
|
|
||||||
if len(silences) > 2:
|
if len(silences) > 2:
|
||||||
|
|
|
@ -1,26 +1,10 @@
|
||||||
import logging
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
|
||||||
import mutagen
|
import mutagen
|
||||||
from libretime_shared.files import compute_md5
|
from libretime_shared.files import compute_md5
|
||||||
from mutagen.easyid3 import EasyID3
|
from loguru import logger
|
||||||
|
|
||||||
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]):
|
def analyze_metadata(filepath_: str, metadata: Dict[str, Any]):
|
||||||
|
@ -40,7 +24,7 @@ def analyze_metadata(filepath_: str, metadata: Dict[str, Any]):
|
||||||
# Get audio file metadata
|
# Get audio file metadata
|
||||||
extracted = mutagen.File(filepath, easy=True)
|
extracted = mutagen.File(filepath, easy=True)
|
||||||
if extracted is None:
|
if extracted is None:
|
||||||
logger.warning("no metadata were extracted for %s", filepath)
|
logger.warning(f"no metadata were extracted for {filepath}")
|
||||||
return metadata
|
return metadata
|
||||||
|
|
||||||
metadata["mime"] = extracted.mime[0]
|
metadata["mime"] = extracted.mime[0]
|
||||||
|
@ -85,36 +69,34 @@ def analyze_metadata(filepath_: str, metadata: Dict[str, Any]):
|
||||||
except (AttributeError, KeyError, IndexError):
|
except (AttributeError, KeyError, IndexError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
extracted_tags_mapping = [
|
extracted_tags_mapping = {
|
||||||
("title", "track_title"),
|
"title": "track_title",
|
||||||
("artist", "artist_name"),
|
"artist": "artist_name",
|
||||||
("album", "album_title"),
|
"album": "album_title",
|
||||||
("bpm", "bpm"),
|
"bpm": "bpm",
|
||||||
("composer", "composer"),
|
"composer": "composer",
|
||||||
("conductor", "conductor"),
|
"conductor": "conductor",
|
||||||
("copyright", "copyright"),
|
"copyright": "copyright",
|
||||||
("comment", "comment"),
|
"comment": "comment",
|
||||||
("comment", "comments"),
|
"encoded_by": "encoder",
|
||||||
("comment", "description"),
|
"genre": "genre",
|
||||||
("encoded_by", "encoder"),
|
"isrc": "isrc",
|
||||||
("genre", "genre"),
|
"label": "label",
|
||||||
("isrc", "isrc"),
|
"organization": "label",
|
||||||
("label", "label"),
|
# "length": "length",
|
||||||
("organization", "label"),
|
"language": "language",
|
||||||
# ("length", "length"),
|
"last_modified": "last_modified",
|
||||||
("language", "language"),
|
"mood": "mood",
|
||||||
("last_modified", "last_modified"),
|
"bit_rate": "bit_rate",
|
||||||
("mood", "mood"),
|
"replay_gain": "replaygain",
|
||||||
("bit_rate", "bit_rate"),
|
# "tracknumber": "track_number",
|
||||||
("replay_gain", "replaygain"),
|
# "track_total": "track_total",
|
||||||
# ("tracknumber", "track_number"),
|
"website": "website",
|
||||||
# ("track_total", "track_total"),
|
"date": "year",
|
||||||
("website", "website"),
|
# "mime_type": "mime",
|
||||||
("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:
|
try:
|
||||||
metadata[metadata_key] = extracted[extracted_key]
|
metadata[metadata_key] = extracted[extracted_key]
|
||||||
if isinstance(metadata[metadata_key], list):
|
if isinstance(metadata[metadata_key], list):
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import logging
|
|
||||||
from subprocess import CalledProcessError
|
from subprocess import CalledProcessError
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
|
||||||
from ._liquidsoap import _liquidsoap
|
from loguru import logger
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
from ._liquidsoap import _liquidsoap
|
||||||
|
|
||||||
|
|
||||||
class UnplayableFileError(Exception):
|
class UnplayableFileError(Exception):
|
||||||
|
@ -27,6 +26,6 @@ def analyze_playability(filename: str, metadata: Dict[str, Any]):
|
||||||
raise UnplayableFileError() from exception
|
raise UnplayableFileError() from exception
|
||||||
|
|
||||||
except OSError as exception: # liquidsoap was not found
|
except OSError as exception: # liquidsoap was not found
|
||||||
logger.warning("Failed to run: %s. Is liquidsoap installed?", exception)
|
logger.warning(f"Failed to run: {exception}. Is liquidsoap installed?")
|
||||||
|
|
||||||
return metadata
|
return metadata
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import logging
|
|
||||||
import shutil
|
import shutil
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
from loguru import logger
|
||||||
|
|
||||||
MAX_DIR_LEN = 48
|
MAX_DIR_LEN = 48
|
||||||
MAX_FILE_LEN = 48
|
MAX_FILE_LEN = 48
|
||||||
|
@ -43,12 +42,12 @@ def organise_file(
|
||||||
return metadata
|
return metadata
|
||||||
|
|
||||||
dest_path = dest_path.with_name(f"{dest_path.stem}_{uuid4()}{dest_path.suffix}")
|
dest_path = dest_path.with_name(f"{dest_path.stem}_{uuid4()}{dest_path.suffix}")
|
||||||
logger.warning("found existing file, using new filepath %s", dest_path)
|
logger.warning(f"found existing file, using new filepath {dest_path}")
|
||||||
|
|
||||||
# Import
|
# Import
|
||||||
dest_path.parent.mkdir(parents=True, exist_ok=True)
|
dest_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
logger.debug("moving %s to %s", filepath, dest_path)
|
logger.debug(f"moving {filepath} to {dest_path}")
|
||||||
shutil.move(filepath, dest_path)
|
shutil.move(filepath, dest_path)
|
||||||
|
|
||||||
metadata["full_path"] = str(dest_path)
|
metadata["full_path"] = str(dest_path)
|
||||||
|
|
|
@ -1,22 +1,21 @@
|
||||||
import logging
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
from typing import Any, Dict, Protocol
|
from typing import Any, Dict
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from loguru import logger
|
||||||
|
from typing_extensions import Protocol
|
||||||
|
|
||||||
from .analyze_cuepoint import analyze_cuepoint, analyze_duration
|
from .analyze_cuepoint import analyze_cuepoint
|
||||||
from .analyze_metadata import analyze_metadata
|
from .analyze_metadata import analyze_metadata
|
||||||
from .analyze_playability import UnplayableFileError, analyze_playability
|
from .analyze_playability import UnplayableFileError, analyze_playability
|
||||||
from .analyze_replaygain import analyze_replaygain
|
from .analyze_replaygain import analyze_replaygain
|
||||||
from .organise_file import organise_file
|
from .organise_file import organise_file
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class Step(Protocol):
|
class Step(Protocol):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __call__(filename: str, metadata: Dict[str, Any]): ...
|
def __call__(filename: str, metadata: Dict[str, Any]):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
class PipelineStatus(int, Enum):
|
class PipelineStatus(int, Enum):
|
||||||
|
@ -25,10 +24,6 @@ class PipelineStatus(int, Enum):
|
||||||
FAILED = 2
|
FAILED = 2
|
||||||
|
|
||||||
|
|
||||||
class PipelineOptions(BaseModel):
|
|
||||||
analyze_cue_points: bool = False
|
|
||||||
|
|
||||||
|
|
||||||
class Pipeline:
|
class Pipeline:
|
||||||
"""Analyzes and imports an audio file into the Airtime library.
|
"""Analyzes and imports an audio file into the Airtime library.
|
||||||
|
|
||||||
|
@ -39,11 +34,10 @@ class Pipeline:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def run_analysis(
|
def run_analysis(
|
||||||
queue: Queue,
|
queue,
|
||||||
audio_file_path: str,
|
audio_file_path,
|
||||||
import_directory: str,
|
import_directory,
|
||||||
original_filename: str,
|
original_filename,
|
||||||
options: PipelineOptions,
|
|
||||||
):
|
):
|
||||||
"""Analyze and import an audio file, and put all extracted metadata into queue.
|
"""Analyze and import an audio file, and put all extracted metadata into queue.
|
||||||
|
|
||||||
|
@ -84,9 +78,7 @@ class Pipeline:
|
||||||
# First, we extract the ID3 tags and other metadata:
|
# First, we extract the ID3 tags and other metadata:
|
||||||
metadata = {}
|
metadata = {}
|
||||||
metadata = analyze_metadata(audio_file_path, metadata)
|
metadata = analyze_metadata(audio_file_path, metadata)
|
||||||
metadata = analyze_duration(audio_file_path, metadata)
|
metadata = analyze_cuepoint(audio_file_path, metadata)
|
||||||
if options.analyze_cue_points:
|
|
||||||
metadata = analyze_cuepoint(audio_file_path, metadata)
|
|
||||||
metadata = analyze_replaygain(audio_file_path, metadata)
|
metadata = analyze_replaygain(audio_file_path, metadata)
|
||||||
metadata = analyze_playability(audio_file_path, metadata)
|
metadata = analyze_playability(audio_file_path, metadata)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import collections
|
import collections
|
||||||
import json
|
import json
|
||||||
import logging
|
|
||||||
import pickle
|
import pickle
|
||||||
import queue
|
import queue
|
||||||
import threading
|
import threading
|
||||||
|
@ -8,10 +7,9 @@ import time
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
from loguru import logger
|
||||||
from requests.exceptions import HTTPError
|
from requests.exceptions import HTTPError
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class PicklableHttpRequest:
|
class PicklableHttpRequest:
|
||||||
def __init__(self, method, url, api_key, data):
|
def __init__(self, method, url, api_key, data):
|
||||||
|
@ -57,7 +55,7 @@ def process_http_requests(ipc_queue, http_retry_queue_path):
|
||||||
# If we fail to unpickle a saved queue of failed HTTP requests, then we'll just log an error
|
# If we fail to unpickle a saved queue of failed HTTP requests, then we'll just log an error
|
||||||
# and continue because those HTTP requests are lost anyways. The pickled file will be
|
# and continue because those HTTP requests are lost anyways. The pickled file will be
|
||||||
# overwritten the next time the analyzer is shut down too.
|
# overwritten the next time the analyzer is shut down too.
|
||||||
logger.error("Failed to unpickle %s. Continuing...", http_retry_queue_path)
|
logger.error(f"Failed to unpickle {http_retry_queue_path}. Continuing...")
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
|
@ -88,12 +86,10 @@ def process_http_requests(ipc_queue, http_retry_queue_path):
|
||||||
with open(http_retry_queue_path, "wb") as pickle_file:
|
with open(http_retry_queue_path, "wb") as pickle_file:
|
||||||
pickle.dump(retry_queue, pickle_file)
|
pickle.dump(retry_queue, pickle_file)
|
||||||
return
|
return
|
||||||
except (
|
except Exception as exception: # Terrible top-level exception handler to prevent the thread from dying, just in case.
|
||||||
Exception
|
|
||||||
) as exception: # Terrible top-level exception handler to prevent the thread from dying, just in case.
|
|
||||||
if shutdown:
|
if shutdown:
|
||||||
return
|
return
|
||||||
logger.exception("Unhandled exception in StatusReporter %s", exception)
|
logger.exception(f"Unhandled exception in StatusReporter {exception}")
|
||||||
logger.info("Restarting StatusReporter thread")
|
logger.info("Restarting StatusReporter thread")
|
||||||
time.sleep(2) # Throttle it
|
time.sleep(2) # Throttle it
|
||||||
|
|
||||||
|
@ -118,7 +114,7 @@ def send_http_request(picklable_request: PicklableHttpRequest, retry_queue):
|
||||||
# The request failed with an error 500 probably, so let's check if Airtime and/or
|
# The request failed with an error 500 probably, so let's check if Airtime and/or
|
||||||
# the web server are broken. If not, then our request was probably causing an
|
# the web server are broken. If not, then our request was probably causing an
|
||||||
# error 500 in the media API (ie. a bug), so there's no point in retrying it.
|
# error 500 in the media API (ie. a bug), so there's no point in retrying it.
|
||||||
logger.exception("HTTP request failed: %s", exception)
|
logger.exception(f"HTTP request failed: {exception}")
|
||||||
parsed_url = urlparse(exception.response.request.url)
|
parsed_url = urlparse(exception.response.request.url)
|
||||||
if is_web_server_broken(parsed_url.scheme + "://" + parsed_url.netloc):
|
if is_web_server_broken(parsed_url.scheme + "://" + parsed_url.netloc):
|
||||||
# If the web server is having problems, retry the request later:
|
# If the web server is having problems, retry the request later:
|
||||||
|
@ -128,12 +124,11 @@ def send_http_request(picklable_request: PicklableHttpRequest, retry_queue):
|
||||||
# notified by sentry.
|
# notified by sentry.
|
||||||
except requests.exceptions.ConnectionError as exception:
|
except requests.exceptions.ConnectionError as exception:
|
||||||
logger.exception(
|
logger.exception(
|
||||||
"HTTP request failed due to a connection error, retrying later: %s",
|
f"HTTP request failed due to a connection error. Retrying later. {exception}"
|
||||||
exception,
|
|
||||||
)
|
)
|
||||||
retry_queue.append(picklable_request) # Retry it later
|
retry_queue.append(picklable_request) # Retry it later
|
||||||
except Exception as exception:
|
except Exception as exception:
|
||||||
logger.exception("HTTP request failed with unhandled exception. %s", exception)
|
logger.exception(f"HTTP request failed with unhandled exception. {exception}")
|
||||||
# Don't put the request into the retry queue, just give up on this one.
|
# Don't put the request into the retry queue, just give up on this one.
|
||||||
# I'm doing this to protect against us getting some pathological request
|
# I'm doing this to protect against us getting some pathological request
|
||||||
# that breaks our code. I don't want us pickling data that potentially
|
# that breaks our code. I don't want us pickling data that potentially
|
||||||
|
@ -215,7 +210,7 @@ class StatusReporter:
|
||||||
audio_metadata["import_status"] = import_status
|
audio_metadata["import_status"] = import_status
|
||||||
audio_metadata["comment"] = reason # hack attack
|
audio_metadata["comment"] = reason # hack attack
|
||||||
put_payload = json.dumps(audio_metadata)
|
put_payload = json.dumps(audio_metadata)
|
||||||
# logger.debug("sending http put with payload: %s", put_payload)
|
# logger.debug("sending http put with payload: " + put_payload)
|
||||||
|
|
||||||
StatusReporter._send_http_request(
|
StatusReporter._send_http_request(
|
||||||
PicklableHttpRequest(
|
PicklableHttpRequest(
|
||||||
|
|
|
@ -1,16 +1,32 @@
|
||||||
# This file contains a list of package dependencies.
|
# This file contains a list of package dependencies.
|
||||||
[python]
|
[python]
|
||||||
python3 = focal, bullseye, jammy, bookworm
|
python3 = buster, bullseye, bookworm, bionic, focal, jammy
|
||||||
python3-pip = focal, bullseye, jammy, bookworm
|
python3-pip = buster, bullseye, bookworm, bionic, focal, jammy
|
||||||
|
python3-pika = buster, bullseye, bookworm, bionic, focal, jammy
|
||||||
|
|
||||||
[liquidsoap]
|
[liquidsoap]
|
||||||
# https://github.com/savonet/liquidsoap/blob/main/CHANGES.md
|
# https://github.com/savonet/liquidsoap/blob/main/CHANGES.md
|
||||||
liquidsoap = focal, bullseye, jammy, bookworm
|
liquidsoap-plugin-alsa = bionic
|
||||||
|
liquidsoap-plugin-ao = bionic
|
||||||
|
liquidsoap-plugin-ogg = bionic
|
||||||
|
liquidsoap-plugin-portaudio = bionic
|
||||||
|
# Already recommended packages in bionic
|
||||||
|
# See `apt show liquidsoap`
|
||||||
|
; liquidsoap-plugin-faad = bionic
|
||||||
|
; liquidsoap-plugin-flac = bionic
|
||||||
|
; liquidsoap-plugin-icecast = bionic
|
||||||
|
; liquidsoap-plugin-lame = bionic
|
||||||
|
; liquidsoap-plugin-mad = bionic
|
||||||
|
; liquidsoap-plugin-pulseaudio = bionic
|
||||||
|
; liquidsoap-plugin-taglib = bionic
|
||||||
|
; liquidsoap-plugin-voaacenc = bionic
|
||||||
|
; liquidsoap-plugin-vorbis = bionic
|
||||||
|
liquidsoap = buster, bullseye, bookworm, bionic, focal, jammy
|
||||||
|
|
||||||
[ffmpeg]
|
[ffmpeg]
|
||||||
# Detect duration, silences and replaygain
|
# Detect duration, silences and replaygain
|
||||||
ffmpeg = focal, bullseye, jammy, bookworm
|
ffmpeg = buster, bullseye, bookworm, bionic, focal, jammy
|
||||||
|
|
||||||
[=development]
|
[=development]
|
||||||
# Generate fixtures
|
# Generate fixtures
|
||||||
ffmpeg = focal, bullseye, jammy, bookworm
|
ffmpeg = buster, bullseye, bookworm, bionic, focal, jammy
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
[tool.isort]
|
|
||||||
profile = "black"
|
|
||||||
combine_as_imports = true
|
|
||||||
known_first_party = ["libretime_analyzer"]
|
|
||||||
|
|
||||||
[tool.pylint.messages_control]
|
[tool.pylint.messages_control]
|
||||||
extension-pkg-whitelist = "pydantic"
|
extension-pkg-whitelist = "pydantic"
|
||||||
disable = [
|
disable = [
|
||||||
|
@ -11,13 +6,13 @@ disable = [
|
||||||
"missing-module-docstring",
|
"missing-module-docstring",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[tool.pylint.format]
|
||||||
|
disable = "logging-fstring-interpolation"
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
log_cli = true
|
log_cli = true
|
||||||
log_cli_level = "DEBUG"
|
log_cli_level = "DEBUG"
|
||||||
|
|
||||||
[tool.coverage.run]
|
|
||||||
source = ["libretime_analyzer"]
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["setuptools", "wheel"]
|
requires = ["setuptools", "wheel"]
|
||||||
build-backend = "setuptools.build_meta"
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Please do not edit this file, edit the setup.py file!
|
# Please do not edit this file, edit the setup.py file!
|
||||||
# This file is auto-generated by tools/extract_requirements.py.
|
# This file is auto-generated by tools/extract_requirements.py.
|
||||||
mutagen>=1.45.1,<1.48
|
mutagen>=1.45.1,<1.46
|
||||||
pika>=1.0.0,<1.4
|
pika>=1.0.0,<1.4
|
||||||
requests>=2.32.2,<2.33
|
requests>=2.25.1,<2.29
|
||||||
typing_extensions
|
typing_extensions
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
from setuptools import find_packages, setup
|
from setuptools import find_packages, setup
|
||||||
|
|
||||||
version = "4.2.0" # x-release-please-version
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="libretime-analyzer",
|
name="libretime-analyzer",
|
||||||
version=version,
|
version="3.0.0-beta.2",
|
||||||
description="Libretime Analyzer",
|
description="Libretime Analyzer",
|
||||||
author="LibreTime Contributors",
|
author="LibreTime Contributors",
|
||||||
url="https://github.com/libretime/libretime",
|
url="https://github.com/libretime/libretime",
|
||||||
|
@ -20,20 +18,17 @@ setup(
|
||||||
"libretime-analyzer=libretime_analyzer.main:cli",
|
"libretime-analyzer=libretime_analyzer.main:cli",
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
python_requires=">=3.8",
|
python_requires=">=3.6",
|
||||||
install_requires=[
|
install_requires=[
|
||||||
"mutagen>=1.45.1,<1.48",
|
"mutagen>=1.45.1,<1.46",
|
||||||
"pika>=1.0.0,<1.4",
|
"pika>=1.0.0,<1.4",
|
||||||
"requests>=2.32.2,<2.33",
|
"requests>=2.25.1,<2.29",
|
||||||
"typing_extensions",
|
"typing_extensions",
|
||||||
],
|
],
|
||||||
extras_require={
|
extras_require={
|
||||||
"dev": [
|
"dev": [
|
||||||
"distro>=1.8.0,<2",
|
"distro",
|
||||||
"types-requests>=2.31.0,<3",
|
"types-requests",
|
||||||
],
|
|
||||||
"sentry": [
|
|
||||||
"sentry-sdk>=1.15.0,<2",
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
|
|
|
@ -2,11 +2,11 @@ import shutil
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from libretime_shared.logging import setup_logger
|
from libretime_shared.logging import TRACE, setup_logger
|
||||||
|
|
||||||
from .fixtures import fixtures_path
|
from .fixtures import fixtures_path
|
||||||
|
|
||||||
setup_logger("debug")
|
setup_logger(TRACE)
|
||||||
|
|
||||||
AUDIO_FILENAME = "s1-stereo-tagged.mp3"
|
AUDIO_FILENAME = "s1-stereo-tagged.mp3"
|
||||||
AUDIO_FILE = fixtures_path / AUDIO_FILENAME
|
AUDIO_FILE = fixtures_path / AUDIO_FILENAME
|
||||||
|
|
|
@ -23,28 +23,28 @@ FILES = [
|
||||||
# 8s -> 9s: silence
|
# 8s -> 9s: silence
|
||||||
# 9s -> 12s: musik
|
# 9s -> 12s: musik
|
||||||
# 12s -> 15s: pink noise fade out
|
# 12s -> 15s: pink noise fade out
|
||||||
Fixture(here / "s1-jointstereo.mp3", 15.0, 1.4, 15.0, -5.9 ),
|
Fixture(here / "s1-jointstereo.mp3", 15.0, 6.0, 13.0, -5.9 ),
|
||||||
Fixture(here / "s1-mono.mp3", 15.0, 1.5, 15.0, -2.0 ),
|
Fixture(here / "s1-mono.mp3", 15.0, 6.0, 13.0, -2.0 ),
|
||||||
Fixture(here / "s1-stereo.mp3", 15.0, 1.4, 15.0, -5.9 ),
|
Fixture(here / "s1-stereo.mp3", 15.0, 6.0, 13.0, -5.9 ),
|
||||||
Fixture(here / "s1-mono-12.mp3", 15.0, 1.2, 15.0, +7.0 ),
|
Fixture(here / "s1-mono-12.mp3", 15.0, 9.0, 12.0, +7.0 ),
|
||||||
Fixture(here / "s1-stereo-12.mp3", 15.0, 1.2, 15.0, +6.1 ),
|
Fixture(here / "s1-stereo-12.mp3", 15.0, 9.0, 12.0, +6.1 ),
|
||||||
Fixture(here / "s1-mono+12.mp3", 15.0, 1.2, 15.0, -17.0 ),
|
Fixture(here / "s1-mono+12.mp3", 15.0, 3.5, 13.0, -17.0 ),
|
||||||
Fixture(here / "s1-stereo+12.mp3", 15.0, 1.2, 15.0, -17.8 ),
|
Fixture(here / "s1-stereo+12.mp3", 15.0, 3.5, 13.0, -17.8 ),
|
||||||
Fixture(here / "s1-mono.flac", 15.0, 1.4, 15.0, -2.3 ),
|
Fixture(here / "s1-mono.flac", 15.0, 6.0, 13.0, -2.3 ),
|
||||||
Fixture(here / "s1-stereo.flac", 15.0, 1.4, 15.0, -6.0 ),
|
Fixture(here / "s1-stereo.flac", 15.0, 6.0, 13.0, -6.0 ),
|
||||||
Fixture(here / "s1-mono-12.flac", 15.0, 2.0, 15.0, +10.0 ),
|
Fixture(here / "s1-mono-12.flac", 15.0, 9.0, 12.0, +10.0 ),
|
||||||
Fixture(here / "s1-stereo-12.flac", 15.0, 1.8, 15.0, +5.9 ),
|
Fixture(here / "s1-stereo-12.flac", 15.0, 9.0, 12.0, +5.9 ),
|
||||||
Fixture(here / "s1-mono+12.flac", 15.0, 0.0, 15.0, -12.0 ),
|
Fixture(here / "s1-mono+12.flac", 15.0, 3.5, 13.0, -12.0 ),
|
||||||
Fixture(here / "s1-stereo+12.flac", 15.0, 0.0, 15.0, -14.9 ),
|
Fixture(here / "s1-stereo+12.flac", 15.0, 3.5, 13.0, -14.9 ),
|
||||||
Fixture(here / "s1-mono.m4a", 15.0, 1.4, 15.0, -4.5 ),
|
Fixture(here / "s1-mono.m4a", 15.0, 6.0, 13.0, -4.5 ),
|
||||||
Fixture(here / "s1-stereo.m4a", 15.0, 1.4, 15.0, -5.8 ),
|
Fixture(here / "s1-stereo.m4a", 15.0, 6.0, 13.0, -5.8 ),
|
||||||
Fixture(here / "s1-mono.ogg", 15.0, 1.4, 15.0, -4.9 ),
|
Fixture(here / "s1-mono.ogg", 15.0, 6.0, 13.0, -4.9 ),
|
||||||
Fixture(here / "s1-stereo.ogg", 15.0, 1.4, 15.0, -5.7 ),
|
Fixture(here / "s1-stereo.ogg", 15.0, 6.0, 13.0, -5.7 ),
|
||||||
Fixture(here / "s1-stereo", 15.0, 1.4, 15.0, -5.7 ),
|
Fixture(here / "s1-stereo", 15.0, 6.0, 13.0, -5.7 ),
|
||||||
Fixture(here / "s1-mono.wav", 15.0, 1.5, 15.0, -2.3 ),
|
Fixture(here / "s1-mono.wav", 15.0, 6.0, 13.0, -2.3 ),
|
||||||
Fixture(here / "s1-stereo.wav", 15.0, 1.4, 15.0, -6.0 ),
|
Fixture(here / "s1-stereo.wav", 15.0, 6.0, 13.0, -6.0 ),
|
||||||
# sample 1 large (looped for 2 hours)
|
# 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
|
# sample 2
|
||||||
# 0s -> 1.8s: silence
|
# 0s -> 1.8s: silence
|
||||||
# 1.8s : noise
|
# 1.8s : noise
|
||||||
|
@ -96,18 +96,12 @@ tags = {
|
||||||
"comment": "Test Comment",
|
"comment": "Test Comment",
|
||||||
}
|
}
|
||||||
|
|
||||||
mp3Tags = {
|
|
||||||
**tags,
|
|
||||||
"comments": tags["comment"],
|
|
||||||
"description": tags["comment"],
|
|
||||||
}
|
|
||||||
|
|
||||||
FILES_TAGGED = [
|
FILES_TAGGED = [
|
||||||
FixtureMeta(
|
FixtureMeta(
|
||||||
here / "s1-jointstereo-tagged.mp3",
|
here / "s1-jointstereo-tagged.mp3",
|
||||||
{
|
{
|
||||||
**meta,
|
**meta,
|
||||||
**mp3Tags,
|
**tags,
|
||||||
"bit_rate": approx(128000, abs=1e2),
|
"bit_rate": approx(128000, abs=1e2),
|
||||||
"channels": 2,
|
"channels": 2,
|
||||||
"mime": "audio/mp3",
|
"mime": "audio/mp3",
|
||||||
|
@ -117,7 +111,7 @@ FILES_TAGGED = [
|
||||||
here / "s1-mono-tagged.mp3",
|
here / "s1-mono-tagged.mp3",
|
||||||
{
|
{
|
||||||
**meta,
|
**meta,
|
||||||
**mp3Tags,
|
**tags,
|
||||||
"bit_rate": approx(64000, abs=1e2),
|
"bit_rate": approx(64000, abs=1e2),
|
||||||
"channels": 1,
|
"channels": 1,
|
||||||
"mime": "audio/mp3",
|
"mime": "audio/mp3",
|
||||||
|
@ -127,7 +121,7 @@ FILES_TAGGED = [
|
||||||
here / "s1-stereo-tagged.mp3",
|
here / "s1-stereo-tagged.mp3",
|
||||||
{
|
{
|
||||||
**meta,
|
**meta,
|
||||||
**mp3Tags,
|
**tags,
|
||||||
"bit_rate": approx(128000, abs=1e2),
|
"bit_rate": approx(128000, abs=1e2),
|
||||||
"channels": 2,
|
"channels": 2,
|
||||||
"mime": "audio/mp3",
|
"mime": "audio/mp3",
|
||||||
|
@ -157,7 +151,7 @@ FILES_TAGGED = [
|
||||||
here / "s1-mono-tagged.m4a",
|
here / "s1-mono-tagged.m4a",
|
||||||
{
|
{
|
||||||
**meta,
|
**meta,
|
||||||
**mp3Tags,
|
**tags,
|
||||||
"bit_rate": approx(65000, abs=5e4),
|
"bit_rate": approx(65000, abs=5e4),
|
||||||
"channels": 2, # Weird
|
"channels": 2, # Weird
|
||||||
"mime": "audio/mp4",
|
"mime": "audio/mp4",
|
||||||
|
@ -167,7 +161,7 @@ FILES_TAGGED = [
|
||||||
here / "s1-stereo-tagged.m4a",
|
here / "s1-stereo-tagged.m4a",
|
||||||
{
|
{
|
||||||
**meta,
|
**meta,
|
||||||
**mp3Tags,
|
**tags,
|
||||||
"bit_rate": approx(128000, abs=1e5),
|
"bit_rate": approx(128000, abs=1e5),
|
||||||
"channels": 2,
|
"channels": 2,
|
||||||
"mime": "audio/mp4",
|
"mime": "audio/mp4",
|
||||||
|
@ -207,7 +201,7 @@ FILES_TAGGED = [
|
||||||
here / "s1-mono-tagged.wav",
|
here / "s1-mono-tagged.wav",
|
||||||
{
|
{
|
||||||
**meta,
|
**meta,
|
||||||
"bit_rate": approx(768000, abs=1e2),
|
"bit_rate": approx(96000, abs=1e2),
|
||||||
"channels": 1,
|
"channels": 1,
|
||||||
"mime": "audio/wav",
|
"mime": "audio/wav",
|
||||||
},
|
},
|
||||||
|
@ -216,7 +210,7 @@ FILES_TAGGED = [
|
||||||
here / "s1-stereo-tagged.wav",
|
here / "s1-stereo-tagged.wav",
|
||||||
{
|
{
|
||||||
**meta,
|
**meta,
|
||||||
"bit_rate": approx(1536000, abs=1e2),
|
"bit_rate": approx(384000, abs=1e2),
|
||||||
"channels": 2,
|
"channels": 2,
|
||||||
"mime": "audio/wav",
|
"mime": "audio/wav",
|
||||||
},
|
},
|
||||||
|
@ -234,18 +228,12 @@ tags = {
|
||||||
"comment": "Ł Ą Ż Ę Ć Ń Ś Ź",
|
"comment": "Ł Ą Ż Ę Ć Ń Ś Ź",
|
||||||
}
|
}
|
||||||
|
|
||||||
mp3Tags = {
|
|
||||||
**tags,
|
|
||||||
"comments": tags["comment"],
|
|
||||||
"description": tags["comment"],
|
|
||||||
}
|
|
||||||
|
|
||||||
FILES_TAGGED += [
|
FILES_TAGGED += [
|
||||||
FixtureMeta(
|
FixtureMeta(
|
||||||
here / "s1-jointstereo-tagged-utf8.mp3",
|
here / "s1-jointstereo-tagged-utf8.mp3",
|
||||||
{
|
{
|
||||||
**meta,
|
**meta,
|
||||||
**mp3Tags,
|
**tags,
|
||||||
"bit_rate": approx(128000, abs=1e2),
|
"bit_rate": approx(128000, abs=1e2),
|
||||||
"channels": 2,
|
"channels": 2,
|
||||||
"mime": "audio/mp3",
|
"mime": "audio/mp3",
|
||||||
|
@ -255,7 +243,7 @@ FILES_TAGGED += [
|
||||||
here / "s1-mono-tagged-utf8.mp3",
|
here / "s1-mono-tagged-utf8.mp3",
|
||||||
{
|
{
|
||||||
**meta,
|
**meta,
|
||||||
**mp3Tags,
|
**tags,
|
||||||
"bit_rate": approx(64000, abs=1e2),
|
"bit_rate": approx(64000, abs=1e2),
|
||||||
"channels": 1,
|
"channels": 1,
|
||||||
"mime": "audio/mp3",
|
"mime": "audio/mp3",
|
||||||
|
@ -265,7 +253,7 @@ FILES_TAGGED += [
|
||||||
here / "s1-stereo-tagged-utf8.mp3",
|
here / "s1-stereo-tagged-utf8.mp3",
|
||||||
{
|
{
|
||||||
**meta,
|
**meta,
|
||||||
**mp3Tags,
|
**tags,
|
||||||
"bit_rate": approx(128000, abs=1e2),
|
"bit_rate": approx(128000, abs=1e2),
|
||||||
"channels": 2,
|
"channels": 2,
|
||||||
"mime": "audio/mp3",
|
"mime": "audio/mp3",
|
||||||
|
@ -295,7 +283,7 @@ FILES_TAGGED += [
|
||||||
here / "s1-mono-tagged-utf8.m4a",
|
here / "s1-mono-tagged-utf8.m4a",
|
||||||
{
|
{
|
||||||
**meta,
|
**meta,
|
||||||
**mp3Tags,
|
**tags,
|
||||||
"bit_rate": approx(65000, abs=5e4),
|
"bit_rate": approx(65000, abs=5e4),
|
||||||
"channels": 2, # Weird
|
"channels": 2, # Weird
|
||||||
"mime": "audio/mp4",
|
"mime": "audio/mp4",
|
||||||
|
@ -305,7 +293,7 @@ FILES_TAGGED += [
|
||||||
here / "s1-stereo-tagged-utf8.m4a",
|
here / "s1-stereo-tagged-utf8.m4a",
|
||||||
{
|
{
|
||||||
**meta,
|
**meta,
|
||||||
**mp3Tags,
|
**tags,
|
||||||
"bit_rate": approx(128000, abs=1e5),
|
"bit_rate": approx(128000, abs=1e5),
|
||||||
"channels": 2,
|
"channels": 2,
|
||||||
"mime": "audio/mp4",
|
"mime": "audio/mp4",
|
||||||
|
@ -345,7 +333,7 @@ FILES_TAGGED += [
|
||||||
here / "s1-mono-tagged-utf8.wav",
|
here / "s1-mono-tagged-utf8.wav",
|
||||||
{
|
{
|
||||||
**meta,
|
**meta,
|
||||||
"bit_rate": approx(768000, abs=1e2),
|
"bit_rate": approx(96000, abs=1e2),
|
||||||
"channels": 1,
|
"channels": 1,
|
||||||
"mime": "audio/wav",
|
"mime": "audio/wav",
|
||||||
},
|
},
|
||||||
|
@ -354,7 +342,7 @@ FILES_TAGGED += [
|
||||||
here / "s1-stereo-tagged-utf8.wav",
|
here / "s1-stereo-tagged-utf8.wav",
|
||||||
{
|
{
|
||||||
**meta,
|
**meta,
|
||||||
"bit_rate": approx(1536000, abs=1e2),
|
"bit_rate": approx(384000, abs=1e2),
|
||||||
"channels": 2,
|
"channels": 2,
|
||||||
"mime": "audio/wav",
|
"mime": "audio/wav",
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
|
import distro
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from libretime_analyzer.pipeline.analyze_cuepoint import (
|
from libretime_analyzer.pipeline.analyze_cuepoint import analyze_cuepoint
|
||||||
analyze_cuepoint,
|
|
||||||
analyze_duration,
|
|
||||||
)
|
|
||||||
|
|
||||||
from ..fixtures import FILES
|
from ..fixtures import FILES
|
||||||
|
|
||||||
|
@ -18,8 +16,11 @@ from ..fixtures import FILES
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_analyze_cuepoint(filepath, length, cuein, cueout):
|
def test_analyze_cuepoint(filepath, length, cuein, cueout):
|
||||||
metadata = analyze_duration(filepath, {})
|
metadata = analyze_cuepoint(filepath, {})
|
||||||
metadata = analyze_cuepoint(filepath, metadata)
|
|
||||||
|
# On bionic, large file duration is a wrong.
|
||||||
|
if distro.codename() == "bionic" and str(filepath).endswith("s1-large.flac"):
|
||||||
|
return
|
||||||
|
|
||||||
assert metadata["length_seconds"] == pytest.approx(length, abs=0.1)
|
assert metadata["length_seconds"] == pytest.approx(length, abs=0.1)
|
||||||
assert float(metadata["cuein"]) == pytest.approx(float(cuein), abs=1)
|
assert float(metadata["cuein"]) == pytest.approx(float(cuein), abs=1)
|
||||||
|
|
|
@ -27,8 +27,8 @@ def test_analyze_metadata(filepath: Path, metadata: dict):
|
||||||
del metadata["length"]
|
del metadata["length"]
|
||||||
del found["length"]
|
del found["length"]
|
||||||
|
|
||||||
# ogg,flac files does not support comments yet
|
# mp3,ogg,flac files does not support comments yet
|
||||||
if not filepath.suffix == ".m4a" and not filepath.suffix == ".mp3":
|
if not filepath.suffix == ".m4a":
|
||||||
if "comment" in metadata:
|
if "comment" in metadata:
|
||||||
del metadata["comment"]
|
del metadata["comment"]
|
||||||
|
|
||||||
|
|
|
@ -33,8 +33,8 @@ def test_analyze_playability_invalid_filepath():
|
||||||
|
|
||||||
|
|
||||||
def test_analyze_playability_invalid_wma():
|
def test_analyze_playability_invalid_wma():
|
||||||
# Liquisoap does not fail with wma files on focal, bullseye, jammy
|
# Liquisoap does not fail with wma files on buster, bullseye, focal, jammy
|
||||||
if distro.codename() in ("focal", "bullseye", "jammy"):
|
if distro.codename() in ("buster", "bullseye", "focal", "jammy"):
|
||||||
return
|
return
|
||||||
|
|
||||||
with pytest.raises(UnplayableFileError):
|
with pytest.raises(UnplayableFileError):
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import distro
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from libretime_analyzer.pipeline.analyze_replaygain import analyze_replaygain
|
from libretime_analyzer.pipeline.analyze_replaygain import analyze_replaygain
|
||||||
|
@ -12,5 +13,10 @@ from ..fixtures import FILES
|
||||||
def test_analyze_replaygain(filepath, replaygain):
|
def test_analyze_replaygain(filepath, replaygain):
|
||||||
tolerance = 0.8
|
tolerance = 0.8
|
||||||
|
|
||||||
|
# On bionic, replaygain is a bit higher for loud mp3 files.
|
||||||
|
# This huge tolerance makes the test pass, with values devianting from ~-17 to ~-13
|
||||||
|
if distro.codename() == "bionic" and str(filepath).endswith("+12.mp3"):
|
||||||
|
tolerance = 5
|
||||||
|
|
||||||
metadata = analyze_replaygain(filepath, {})
|
metadata = analyze_replaygain(filepath, {})
|
||||||
assert metadata["replay_gain"] == pytest.approx(replaygain, abs=tolerance)
|
assert metadata["replay_gain"] == pytest.approx(replaygain, abs=tolerance)
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
from math import inf
|
||||||
|
|
||||||
|
import distro
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from libretime_analyzer.pipeline._ffmpeg import (
|
from libretime_analyzer.pipeline._ffmpeg import (
|
||||||
|
@ -27,6 +30,11 @@ def test_probe_replaygain(filepath, replaygain):
|
||||||
def test_compute_replaygain(filepath, replaygain):
|
def test_compute_replaygain(filepath, replaygain):
|
||||||
tolerance = 0.8
|
tolerance = 0.8
|
||||||
|
|
||||||
|
# On bionic, replaygain is a bit higher for loud mp3 files.
|
||||||
|
# This huge tolerance makes the test pass, with values devianting from ~-17 to ~-13
|
||||||
|
if distro.codename() == "bionic" and str(filepath).endswith("+12.mp3"):
|
||||||
|
tolerance = 5
|
||||||
|
|
||||||
assert compute_replaygain(filepath) == pytest.approx(replaygain, abs=tolerance)
|
assert compute_replaygain(filepath) == pytest.approx(replaygain, abs=tolerance)
|
||||||
|
|
||||||
|
|
||||||
|
@ -78,6 +86,10 @@ def test_silence_detect_re(line, expected):
|
||||||
def test_compute_silences(filepath, length, cuein, cueout):
|
def test_compute_silences(filepath, length, cuein, cueout):
|
||||||
result = compute_silences(filepath)
|
result = compute_silences(filepath)
|
||||||
|
|
||||||
|
# On bionic, large file duration is a wrong.
|
||||||
|
if distro.codename() == "bionic" and str(filepath).endswith("s1-large.flac"):
|
||||||
|
return
|
||||||
|
|
||||||
if cuein != 0.0:
|
if cuein != 0.0:
|
||||||
assert len(result) > 0
|
assert len(result) > 0
|
||||||
first = result.pop(0)
|
first = result.pop(0)
|
||||||
|
@ -85,6 +97,11 @@ def test_compute_silences(filepath, length, cuein, cueout):
|
||||||
assert first[1] == pytest.approx(cuein, abs=1)
|
assert first[1] == pytest.approx(cuein, abs=1)
|
||||||
|
|
||||||
if cueout != length:
|
if cueout != length:
|
||||||
|
# ffmpeg v3 (bionic) does not warn about silence end when the track ends.
|
||||||
|
# Check for infinity on last silence ending
|
||||||
|
if distro.codename() == "bionic":
|
||||||
|
length = inf
|
||||||
|
|
||||||
assert len(result) > 0
|
assert len(result) > 0
|
||||||
last = result.pop()
|
last = result.pop()
|
||||||
assert last[0] == pytest.approx(cueout, abs=1)
|
assert last[0] == pytest.approx(cueout, abs=1)
|
||||||
|
@ -96,4 +113,8 @@ def test_compute_silences(filepath, length, cuein, cueout):
|
||||||
map(lambda i: pytest.param(i.path, i.length, id=i.path.name), FILES),
|
map(lambda i: pytest.param(i.path, i.length, id=i.path.name), FILES),
|
||||||
)
|
)
|
||||||
def test_probe_duration(filepath, length):
|
def test_probe_duration(filepath, length):
|
||||||
|
# On bionic, large file duration is a wrong.
|
||||||
|
if distro.codename() == "bionic" and str(filepath).endswith("s1-large.flac"):
|
||||||
|
return
|
||||||
|
|
||||||
assert probe_duration(filepath) == pytest.approx(length, abs=0.05)
|
assert probe_duration(filepath) == pytest.approx(length, abs=0.05)
|
||||||
|
|
|
@ -4,7 +4,7 @@ from queue import Queue
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from libretime_analyzer.pipeline import Pipeline, PipelineOptions
|
from libretime_analyzer.pipeline import Pipeline
|
||||||
|
|
||||||
from ..conftest import AUDIO_FILENAME, AUDIO_IMPORT_DEST
|
from ..conftest import AUDIO_FILENAME, AUDIO_IMPORT_DEST
|
||||||
|
|
||||||
|
@ -16,7 +16,6 @@ def test_run_analysis(src_dir: Path, dest_dir: Path):
|
||||||
str(src_dir / AUDIO_FILENAME),
|
str(src_dir / AUDIO_FILENAME),
|
||||||
str(dest_dir),
|
str(dest_dir),
|
||||||
AUDIO_FILENAME,
|
AUDIO_FILENAME,
|
||||||
PipelineOptions(),
|
|
||||||
)
|
)
|
||||||
metadata = queue.get()
|
metadata = queue.get()
|
||||||
|
|
||||||
|
|
|
@ -5,12 +5,12 @@ include ../tools/python.mk
|
||||||
PIP_INSTALL := \
|
PIP_INSTALL := \
|
||||||
--editable ../shared \
|
--editable ../shared \
|
||||||
--editable .[dev]
|
--editable .[dev]
|
||||||
PYLINT_ARG := libretime_api_client tests
|
PYLINT_ARG := libretime_api_client tests || true
|
||||||
MYPY_ARG := libretime_api_client tests
|
MYPY_ARG := libretime_api_client tests || true
|
||||||
BANDIT_ARG := libretime_api_client
|
BANDIT_ARG := libretime_api_client || true
|
||||||
|
PYTEST_ARG := --cov=libretime_api_client tests
|
||||||
|
|
||||||
format: .format
|
format: .format
|
||||||
lint: .format-check .pylint .mypy .bandit
|
lint: .format-check .pylint .mypy .bandit
|
||||||
test: .pytest
|
test: .pytest
|
||||||
test-coverage: .coverage
|
|
||||||
clean: .clean
|
clean: .clean
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
from importlib.metadata import version as get_version
|
|
||||||
|
|
||||||
PACKAGE = __name__
|
|
||||||
VERSION = get_version(__name__)
|
|
|
@ -1,13 +1,11 @@
|
||||||
import logging
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
from requests import Response, Session as BaseSession
|
from requests import Response, Session as BaseSession
|
||||||
from requests.adapters import HTTPAdapter
|
from requests.adapters import HTTPAdapter
|
||||||
from requests.exceptions import RequestException
|
from requests.exceptions import RequestException
|
||||||
from urllib3.util import Retry
|
from urllib3.util import Retry
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
DEFAULT_TIMEOUT = 5
|
DEFAULT_TIMEOUT = 5
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,26 +24,20 @@ class TimeoutHTTPAdapter(HTTPAdapter):
|
||||||
return super().send(request, *args, **kwargs)
|
return super().send(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def default_retry(max_retries: int = 5):
|
|
||||||
return Retry(
|
|
||||||
total=max_retries,
|
|
||||||
backoff_factor=2,
|
|
||||||
status_forcelist=[413, 429, 500, 502, 503, 504],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Session(BaseSession):
|
class Session(BaseSession):
|
||||||
base_url: Optional[str]
|
base_url: Optional[str]
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, base_url: Optional[str] = None):
|
||||||
self,
|
|
||||||
base_url: Optional[str] = None,
|
|
||||||
retry: Optional[Retry] = None,
|
|
||||||
):
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.base_url = base_url
|
self.base_url = base_url
|
||||||
|
|
||||||
adapter = TimeoutHTTPAdapter(max_retries=retry)
|
retry_strategy = Retry(
|
||||||
|
total=5,
|
||||||
|
backoff_factor=2,
|
||||||
|
status_forcelist=[413, 429, 500, 502, 503, 504],
|
||||||
|
)
|
||||||
|
|
||||||
|
adapter = TimeoutHTTPAdapter(max_retries=retry_strategy)
|
||||||
|
|
||||||
self.mount("http://", adapter)
|
self.mount("http://", adapter)
|
||||||
self.mount("https://", adapter)
|
self.mount("https://", adapter)
|
||||||
|
@ -67,16 +59,9 @@ class AbstractApiClient:
|
||||||
session: Session
|
session: Session
|
||||||
base_url: str
|
base_url: str
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, base_url: str):
|
||||||
self,
|
|
||||||
base_url: str,
|
|
||||||
retry: Optional[Retry] = None,
|
|
||||||
):
|
|
||||||
self.base_url = base_url
|
self.base_url = base_url
|
||||||
self.session = Session(
|
self.session = Session(base_url=base_url)
|
||||||
base_url=base_url,
|
|
||||||
retry=retry,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _request(
|
def _request(
|
||||||
self,
|
self,
|
||||||
|
|
|
@ -0,0 +1,167 @@
|
||||||
|
import logging
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from requests.auth import AuthBase
|
||||||
|
|
||||||
|
|
||||||
|
class UrlParamDict(dict):
|
||||||
|
def __missing__(self, key):
|
||||||
|
return "{" + key + "}"
|
||||||
|
|
||||||
|
|
||||||
|
class UrlException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class IncompleteUrl(UrlException):
|
||||||
|
def __init__(self, url):
|
||||||
|
super().__init__()
|
||||||
|
self.url = url
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Incomplete url: '{self.url}'"
|
||||||
|
|
||||||
|
|
||||||
|
class UrlBadParam(UrlException):
|
||||||
|
def __init__(self, url, param):
|
||||||
|
super().__init__()
|
||||||
|
self.url = url
|
||||||
|
self.param = param
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Bad param '{self.param}' passed into url: '{self.url}'"
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=too-few-public-methods
|
||||||
|
class KeyAuth(AuthBase):
|
||||||
|
def __init__(self, key):
|
||||||
|
self.key = key
|
||||||
|
|
||||||
|
def __call__(self, r):
|
||||||
|
r.headers["Authorization"] = f"Api-Key {self.key}"
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
class ApcUrl:
|
||||||
|
"""A safe abstraction and testable for filling in parameters in
|
||||||
|
api_client.cfg"""
|
||||||
|
|
||||||
|
def __init__(self, base_url):
|
||||||
|
self.base_url = base_url
|
||||||
|
|
||||||
|
def params(self, **params):
|
||||||
|
temp_url = self.base_url
|
||||||
|
for k in params:
|
||||||
|
wrapped_param = "{" + k + "}"
|
||||||
|
if wrapped_param not in temp_url:
|
||||||
|
raise UrlBadParam(self.base_url, k)
|
||||||
|
temp_url = temp_url.format_map(UrlParamDict(**params))
|
||||||
|
return ApcUrl(temp_url)
|
||||||
|
|
||||||
|
def url(self):
|
||||||
|
if "{" in self.base_url:
|
||||||
|
raise IncompleteUrl(self.base_url)
|
||||||
|
return self.base_url
|
||||||
|
|
||||||
|
|
||||||
|
class ApiRequest:
|
||||||
|
API_HTTP_REQUEST_TIMEOUT = 30 # 30 second HTTP request timeout
|
||||||
|
|
||||||
|
def __init__(self, name, url, logger=None, api_key=None):
|
||||||
|
self.name = name
|
||||||
|
self.url = url
|
||||||
|
self.__req = None
|
||||||
|
if logger is None:
|
||||||
|
self.logger = logging
|
||||||
|
else:
|
||||||
|
self.logger = logger
|
||||||
|
self.auth = KeyAuth(api_key)
|
||||||
|
|
||||||
|
def __call__(self, *, _post_data=None, _put_data=None, params=None, **kwargs):
|
||||||
|
final_url = self.url.params(**kwargs).url()
|
||||||
|
self.logger.debug(final_url)
|
||||||
|
try:
|
||||||
|
if _post_data is not None:
|
||||||
|
res = requests.post(
|
||||||
|
final_url,
|
||||||
|
data=_post_data,
|
||||||
|
auth=self.auth,
|
||||||
|
timeout=ApiRequest.API_HTTP_REQUEST_TIMEOUT,
|
||||||
|
)
|
||||||
|
elif _put_data is not None:
|
||||||
|
res = requests.put(
|
||||||
|
final_url,
|
||||||
|
data=_put_data,
|
||||||
|
auth=self.auth,
|
||||||
|
timeout=ApiRequest.API_HTTP_REQUEST_TIMEOUT,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
res = requests.get(
|
||||||
|
final_url,
|
||||||
|
params=params,
|
||||||
|
auth=self.auth,
|
||||||
|
timeout=ApiRequest.API_HTTP_REQUEST_TIMEOUT,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check for bad HTTP status code
|
||||||
|
res.raise_for_status()
|
||||||
|
|
||||||
|
if "application/json" in res.headers["content-type"]:
|
||||||
|
return res.json()
|
||||||
|
return res
|
||||||
|
except requests.exceptions.Timeout:
|
||||||
|
self.logger.error("HTTP request to %s timed out", final_url)
|
||||||
|
raise
|
||||||
|
except requests.exceptions.HTTPError:
|
||||||
|
self.logger.error(
|
||||||
|
f"{res.request.method} {res.request.url} request failed '{res.status_code}':"
|
||||||
|
f"\nPayload: {res.request.body}"
|
||||||
|
f"\nResponse: {res.text}"
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
|
||||||
|
def req(self, *args, **kwargs):
|
||||||
|
self.__req = lambda: self(*args, **kwargs)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def retry(self, count, delay=5):
|
||||||
|
"""Try to send request n times. If after n times it fails then
|
||||||
|
we finally raise exception"""
|
||||||
|
for _ in range(0, count - 1):
|
||||||
|
try:
|
||||||
|
return self.__req()
|
||||||
|
except requests.exceptions.RequestException:
|
||||||
|
sleep(delay)
|
||||||
|
return self.__req()
|
||||||
|
|
||||||
|
|
||||||
|
class RequestProvider:
|
||||||
|
"""
|
||||||
|
Creates the available ApiRequest instance
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, base_url: str, api_key: str, endpoints: dict):
|
||||||
|
self.requests = {}
|
||||||
|
self.url = ApcUrl(base_url + "/{action}")
|
||||||
|
|
||||||
|
# Now we must discover the possible actions
|
||||||
|
for action_name, action_value in endpoints.items():
|
||||||
|
new_url = self.url.params(action=action_value)
|
||||||
|
if "{api_key}" in action_value:
|
||||||
|
new_url = new_url.params(api_key=api_key)
|
||||||
|
self.requests[action_name] = ApiRequest(
|
||||||
|
action_name, new_url, api_key=api_key
|
||||||
|
)
|
||||||
|
|
||||||
|
def available_requests(self):
|
||||||
|
return list(self.requests.keys())
|
||||||
|
|
||||||
|
def __contains__(self, request):
|
||||||
|
return request in self.requests
|
||||||
|
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
if attr in self:
|
||||||
|
return self.requests[attr]
|
||||||
|
|
||||||
|
return super().__getattribute__(attr)
|
|
@ -1,150 +1,124 @@
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from functools import wraps
|
import time
|
||||||
from time import sleep
|
import urllib.parse
|
||||||
|
|
||||||
from requests.exceptions import RequestException
|
import requests
|
||||||
|
from libretime_shared.config import BaseConfig, GeneralConfig
|
||||||
|
|
||||||
from ._client import AbstractApiClient, Response
|
from ._utils import ApiRequest, RequestProvider
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def retry_decorator(max_retries: int = 5):
|
class Config(BaseConfig):
|
||||||
def retry_request(func):
|
general: GeneralConfig
|
||||||
@wraps(func)
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
retries = max_retries
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
return func(*args, **kwargs)
|
|
||||||
except RequestException as exception:
|
|
||||||
logger.warning(exception)
|
|
||||||
|
|
||||||
retries -= 1
|
|
||||||
if retries <= 0:
|
|
||||||
break
|
|
||||||
|
|
||||||
sleep(2.0)
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
return retry_request
|
|
||||||
|
|
||||||
|
|
||||||
class BaseApiClient(AbstractApiClient):
|
AIRTIME_API_VERSION = "1.1"
|
||||||
def __init__(self, base_url: str, api_key: str):
|
|
||||||
super().__init__(base_url=base_url)
|
|
||||||
self.session.headers.update({"Authorization": f"Api-Key {api_key}"})
|
|
||||||
self.session.params.update({"format": "json"}) # type: ignore[union-attr]
|
|
||||||
|
|
||||||
def version(self, **kwargs) -> Response:
|
|
||||||
return self._request(
|
|
||||||
"GET",
|
|
||||||
"/api/version",
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
def register_component(self, component: str, **kwargs) -> Response:
|
api_endpoints = {}
|
||||||
return self._request(
|
|
||||||
"GET",
|
|
||||||
"/api/register-component",
|
|
||||||
params={"component": component},
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
def notify_media_item_start_play(self, media_id, **kwargs) -> Response:
|
# URL to get the version number of the server API
|
||||||
return self._request(
|
api_endpoints["version_url"] = "version/api_key/{api_key}"
|
||||||
"GET",
|
# URL to register a components IP Address with the central web server
|
||||||
"/api/notify-media-item-start-play",
|
api_endpoints[
|
||||||
params={"media_id": media_id},
|
"register_component"
|
||||||
**kwargs,
|
] = "register-component/format/json/api_key/{api_key}/component/{component}"
|
||||||
)
|
|
||||||
|
|
||||||
def update_liquidsoap_status(self, msg, stream_id, boot_time, **kwargs) -> Response:
|
# media-monitor
|
||||||
return self._request(
|
api_endpoints[
|
||||||
"POST",
|
"upload_recorded"
|
||||||
"/api/update-liquidsoap-status",
|
] = "upload-recorded/format/json/api_key/{api_key}/fileid/{fileid}/showinstanceid/{showinstanceid}"
|
||||||
params={"stream_id": stream_id, "boot_time": boot_time},
|
# show-recorder
|
||||||
data={"msg_post": msg},
|
api_endpoints["show_schedule_url"] = "recorded-shows/format/json/api_key/{api_key}"
|
||||||
**kwargs,
|
api_endpoints["upload_file_url"] = "rest/media"
|
||||||
)
|
# pypo
|
||||||
|
api_endpoints[
|
||||||
def update_source_status(self, sourcename, status, **kwargs) -> Response:
|
"update_start_playing_url"
|
||||||
return self._request(
|
] = "notify-media-item-start-play/api_key/{api_key}/media_id/{media_id}/"
|
||||||
"GET",
|
api_endpoints[
|
||||||
"/api/update-source-status",
|
"get_stream_setting"
|
||||||
params={"sourcename": sourcename, "status": status},
|
] = "get-stream-setting/format/json/api_key/{api_key}/"
|
||||||
**kwargs,
|
api_endpoints[
|
||||||
)
|
"update_liquidsoap_status"
|
||||||
|
] = "update-liquidsoap-status/format/json/api_key/{api_key}/msg/{msg}/stream_id/{stream_id}/boot_time/{boot_time}"
|
||||||
def check_live_stream_auth(self, username, password, djtype, **kwargs) -> Response:
|
api_endpoints[
|
||||||
return self._request(
|
"update_source_status"
|
||||||
"GET",
|
] = "update-source-status/format/json/api_key/{api_key}/sourcename/{sourcename}/status/{status}"
|
||||||
"/api/check-live-stream-auth",
|
api_endpoints[
|
||||||
params={"username": username, "password": password, "djtype": djtype},
|
"check_live_stream_auth"
|
||||||
**kwargs,
|
] = "check-live-stream-auth/format/json/api_key/{api_key}/username/{username}/password/{password}/djtype/{djtype}"
|
||||||
)
|
api_endpoints["get_bootstrap_info"] = "get-bootstrap-info/format/json/api_key/{api_key}"
|
||||||
|
api_endpoints[
|
||||||
def notify_webstream_data(self, media_id, data, **kwargs) -> Response:
|
"notify_webstream_data"
|
||||||
return self._request(
|
] = "notify-webstream-data/api_key/{api_key}/media_id/{media_id}/format/json"
|
||||||
"POST",
|
api_endpoints[
|
||||||
"/api/notify-webstream-data",
|
"notify_liquidsoap_started"
|
||||||
params={"media_id": media_id},
|
] = "rabbitmq-do-push/api_key/{api_key}/format/json"
|
||||||
data={"data": data}, # Data is already a json formatted string
|
api_endpoints[
|
||||||
**kwargs,
|
"get_stream_parameters"
|
||||||
)
|
] = "get-stream-parameters/api_key/{api_key}/format/json"
|
||||||
|
api_endpoints["push_stream_stats"] = "push-stream-stats/api_key/{api_key}/format/json"
|
||||||
def rabbitmq_do_push(self, **kwargs) -> Response:
|
api_endpoints[
|
||||||
return self._request(
|
"update_stream_setting_table"
|
||||||
"GET",
|
] = "update-stream-setting-table/api_key/{api_key}/format/json"
|
||||||
"/api/rabbitmq-do-push",
|
api_endpoints[
|
||||||
**kwargs,
|
"update_metadata_on_tunein"
|
||||||
)
|
] = "update-metadata-on-tunein/api_key/{api_key}"
|
||||||
|
|
||||||
def push_stream_stats(self, data, **kwargs) -> Response:
|
|
||||||
return self._request(
|
|
||||||
"POST",
|
|
||||||
"/api/push-stream-stats",
|
|
||||||
data={"data": json.dumps(data)},
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
def update_stream_setting_table(self, data, **kwargs) -> Response:
|
|
||||||
return self._request(
|
|
||||||
"POST",
|
|
||||||
"/api/update-stream-setting-table",
|
|
||||||
data={"data": json.dumps(data)},
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
def update_metadata_on_tunein(self, **kwargs) -> Response:
|
|
||||||
return self._request(
|
|
||||||
"GET",
|
|
||||||
"/api/update-metadata-on-tunein",
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ApiClient:
|
class ApiClient:
|
||||||
def __init__(self, base_url: str, api_key: str):
|
API_BASE = "/api"
|
||||||
self._base_client = BaseApiClient(base_url=base_url, api_key=api_key)
|
UPLOAD_RETRIES = 3
|
||||||
|
UPLOAD_WAIT = 60
|
||||||
|
|
||||||
def version(self):
|
def __init__(self, logger=None, config_path="/etc/libretime/config.yml"):
|
||||||
|
self.logger = logger or logging
|
||||||
|
|
||||||
|
config = Config(config_path)
|
||||||
|
self.base_url = config.general.public_url
|
||||||
|
self.api_key = config.general.api_key
|
||||||
|
|
||||||
|
self.services = RequestProvider(
|
||||||
|
base_url=self.base_url + self.API_BASE,
|
||||||
|
api_key=self.api_key,
|
||||||
|
endpoints=api_endpoints,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __get_api_version(self):
|
||||||
try:
|
try:
|
||||||
resp = self._base_client.version()
|
return self.services.version_url()["api_version"]
|
||||||
payload = resp.json()
|
except Exception as exception:
|
||||||
return payload["api_version"]
|
self.logger.exception(exception)
|
||||||
except RequestException:
|
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
|
def is_server_compatible(self, verbose=True):
|
||||||
|
api_version = self.__get_api_version()
|
||||||
|
if api_version == -1:
|
||||||
|
if verbose:
|
||||||
|
self.logger.info("Unable to get Airtime API version number.\n")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if api_version[0:3] != AIRTIME_API_VERSION[0:3]:
|
||||||
|
if verbose:
|
||||||
|
self.logger.info("Airtime API version found: " + str(api_version))
|
||||||
|
self.logger.info(
|
||||||
|
"pypo is only compatible with API version: " + AIRTIME_API_VERSION
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if verbose:
|
||||||
|
self.logger.info("Airtime API version found: " + str(api_version))
|
||||||
|
self.logger.info(
|
||||||
|
"pypo is only compatible with API version: " + AIRTIME_API_VERSION
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
def notify_liquidsoap_started(self):
|
def notify_liquidsoap_started(self):
|
||||||
try:
|
try:
|
||||||
self._base_client.rabbitmq_do_push()
|
self.services.notify_liquidsoap_started()
|
||||||
except RequestException:
|
except Exception as exception:
|
||||||
pass
|
self.logger.exception(exception)
|
||||||
|
|
||||||
def notify_media_item_start_playing(self, media_id):
|
def notify_media_item_start_playing(self, media_id):
|
||||||
"""
|
"""
|
||||||
|
@ -153,20 +127,96 @@ class ApiClient:
|
||||||
which we handed to liquidsoap in get_liquidsoap_data().
|
which we handed to liquidsoap in get_liquidsoap_data().
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return self._base_client.notify_media_item_start_play(media_id=media_id)
|
return self.services.update_start_playing_url(media_id=media_id)
|
||||||
except RequestException:
|
except Exception as exception:
|
||||||
|
self.logger.exception(exception)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def get_shows_to_record(self):
|
||||||
|
try:
|
||||||
|
return self.services.show_schedule_url()
|
||||||
|
except Exception as exception:
|
||||||
|
self.logger.exception(exception)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def upload_recorded_show(self, files, show_id):
|
||||||
|
response = ""
|
||||||
|
|
||||||
|
retries = self.UPLOAD_RETRIES
|
||||||
|
retries_wait = self.UPLOAD_WAIT
|
||||||
|
|
||||||
|
url = self.construct_rest_url("upload_file_url")
|
||||||
|
|
||||||
|
self.logger.debug(url)
|
||||||
|
|
||||||
|
for i in range(0, retries):
|
||||||
|
self.logger.debug("Upload attempt: %s", i + 1)
|
||||||
|
self.logger.debug(files)
|
||||||
|
self.logger.debug(ApiRequest.API_HTTP_REQUEST_TIMEOUT)
|
||||||
|
|
||||||
|
try:
|
||||||
|
request = requests.post(
|
||||||
|
url, files=files, timeout=float(ApiRequest.API_HTTP_REQUEST_TIMEOUT)
|
||||||
|
)
|
||||||
|
response = request.json()
|
||||||
|
self.logger.debug(response)
|
||||||
|
|
||||||
|
# FIXME: We need to tell LibreTime that the uploaded track was recorded
|
||||||
|
# for a specific show
|
||||||
|
#
|
||||||
|
# My issue here is that response does not yet have an id. The id gets
|
||||||
|
# generated at the point where analyzer is done with it's work. We
|
||||||
|
# probably need to do what is below in analyzer and also make sure that
|
||||||
|
# the show instance id is routed all the way through.
|
||||||
|
#
|
||||||
|
# It already gets uploaded by this but the RestController does not seem
|
||||||
|
# to care about it. In the end analyzer doesn't have the info in it's
|
||||||
|
# rabbitmq message and imports the show as a regular track.
|
||||||
|
#
|
||||||
|
# logger.info("uploaded show result as file id %s", response.id)
|
||||||
|
#
|
||||||
|
# url = self.construct_url("upload_recorded") url =
|
||||||
|
# url.replace('%%fileid%%', response.id) url =
|
||||||
|
# url.replace('%%showinstanceid%%', show_id) request.get(url)
|
||||||
|
# logger.info("associated uploaded file %s with show instance %s",
|
||||||
|
# response.id, show_id)
|
||||||
|
break
|
||||||
|
|
||||||
|
except requests.exceptions.HTTPError as exception:
|
||||||
|
self.logger.error(f"Http error code: {exception.response.status_code}")
|
||||||
|
self.logger.exception(exception)
|
||||||
|
|
||||||
|
except requests.exceptions.ConnectionError as exception:
|
||||||
|
self.logger.exception(f"Server is down: {exception}")
|
||||||
|
|
||||||
|
except Exception as exception:
|
||||||
|
self.logger.exception(exception)
|
||||||
|
|
||||||
|
# wait some time before next retry
|
||||||
|
time.sleep(retries_wait)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
def check_live_stream_auth(self, username, password, dj_type):
|
def check_live_stream_auth(self, username, password, dj_type):
|
||||||
try:
|
try:
|
||||||
return self._base_client.check_live_stream_auth(
|
return self.services.check_live_stream_auth(
|
||||||
username=username,
|
username=username, password=password, djtype=dj_type
|
||||||
password=password,
|
|
||||||
djtype=dj_type,
|
|
||||||
)
|
)
|
||||||
except RequestException:
|
except Exception as exception:
|
||||||
|
self.logger.exception(exception)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
def construct_rest_url(self, action_key):
|
||||||
|
"""
|
||||||
|
Constructs the base url for RESTful requests
|
||||||
|
"""
|
||||||
|
url = urllib.parse.urlsplit(self.base_url)
|
||||||
|
url.username = self.api_key
|
||||||
|
return f"{url.geturl()}/{api_endpoints[action_key]}"
|
||||||
|
|
||||||
|
def get_stream_setting(self):
|
||||||
|
return self.services.get_stream_setting()
|
||||||
|
|
||||||
def register_component(self, component):
|
def register_component(self, component):
|
||||||
"""
|
"""
|
||||||
Purpose of this method is to contact the server with a "Hey its
|
Purpose of this method is to contact the server with a "Hey its
|
||||||
|
@ -175,45 +225,71 @@ class ApiClient:
|
||||||
to query monit via monit's http service, or download log files via a
|
to query monit via monit's http service, or download log files via a
|
||||||
http server.
|
http server.
|
||||||
"""
|
"""
|
||||||
return self._base_client.register_component(component=component)
|
return self.services.register_component(component=component)
|
||||||
|
|
||||||
@retry_decorator()
|
|
||||||
def notify_liquidsoap_status(self, msg, stream_id, time):
|
def notify_liquidsoap_status(self, msg, stream_id, time):
|
||||||
self._base_client.update_liquidsoap_status(
|
try:
|
||||||
msg=msg,
|
# encoded_msg is no longer used server_side!!
|
||||||
stream_id=stream_id,
|
encoded_msg = urllib.parse.quote("dummy")
|
||||||
boot_time=time,
|
|
||||||
)
|
self.services.update_liquidsoap_status.req(
|
||||||
|
_post_data={"msg_post": msg},
|
||||||
|
msg=encoded_msg,
|
||||||
|
stream_id=stream_id,
|
||||||
|
boot_time=time,
|
||||||
|
).retry(5)
|
||||||
|
except Exception as exception:
|
||||||
|
self.logger.exception(exception)
|
||||||
|
|
||||||
@retry_decorator()
|
|
||||||
def notify_source_status(self, sourcename, status):
|
def notify_source_status(self, sourcename, status):
|
||||||
return self._base_client.update_source_status(
|
try:
|
||||||
sourcename=sourcename,
|
return self.services.update_source_status.req(
|
||||||
status=status,
|
sourcename=sourcename, status=status
|
||||||
)
|
).retry(5)
|
||||||
|
except Exception as exception:
|
||||||
|
self.logger.exception(exception)
|
||||||
|
|
||||||
|
def get_bootstrap_info(self):
|
||||||
|
"""
|
||||||
|
Retrieve infomations needed on bootstrap time.
|
||||||
|
"""
|
||||||
|
return self.services.get_bootstrap_info()
|
||||||
|
|
||||||
@retry_decorator()
|
|
||||||
def notify_webstream_data(self, data, media_id):
|
def notify_webstream_data(self, data, media_id):
|
||||||
"""
|
"""
|
||||||
Update the server with the latest metadata we've received from the
|
Update the server with the latest metadata we've received from the
|
||||||
external webstream
|
external webstream
|
||||||
"""
|
"""
|
||||||
return self._base_client.notify_webstream_data(
|
self.logger.info(
|
||||||
data=data,
|
self.services.notify_webstream_data.req(
|
||||||
media_id=str(media_id),
|
_post_data={"data": data}, media_id=str(media_id)
|
||||||
|
).retry(5)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_stream_parameters(self):
|
||||||
|
response = self.services.get_stream_parameters()
|
||||||
|
self.logger.debug(response)
|
||||||
|
return response
|
||||||
|
|
||||||
def push_stream_stats(self, data):
|
def push_stream_stats(self, data):
|
||||||
return self._base_client.push_stream_stats(data=data)
|
# TODO : users of this method should do their own error handling
|
||||||
|
response = self.services.push_stream_stats(
|
||||||
|
_post_data={"data": json.dumps(data)}
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
def update_stream_setting_table(self, data):
|
def update_stream_setting_table(self, data):
|
||||||
try:
|
try:
|
||||||
return self._base_client.update_stream_setting_table(data=data)
|
response = self.services.update_stream_setting_table(
|
||||||
except RequestException:
|
_post_data={"data": json.dumps(data)}
|
||||||
return None
|
)
|
||||||
|
return response
|
||||||
|
except Exception as exception:
|
||||||
|
self.logger.exception(exception)
|
||||||
|
|
||||||
def update_metadata_on_tunein(self):
|
def update_metadata_on_tunein(self):
|
||||||
self._base_client.update_metadata_on_tunein()
|
self.services.update_metadata_on_tunein()
|
||||||
|
|
||||||
def trigger_task_manager(self):
|
|
||||||
self._base_client.version()
|
class InvalidContentType(Exception):
|
||||||
|
pass
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
from ._client import AbstractApiClient, Response, default_retry
|
from ._client import AbstractApiClient, Response
|
||||||
|
|
||||||
|
|
||||||
class ApiClient(AbstractApiClient):
|
class ApiClient(AbstractApiClient):
|
||||||
VERSION = "2.0"
|
VERSION = "2.0"
|
||||||
|
|
||||||
def __init__(self, base_url: str, api_key: str):
|
def __init__(self, base_url: str, api_key: str):
|
||||||
super().__init__(
|
super().__init__(base_url=base_url)
|
||||||
base_url=base_url,
|
|
||||||
retry=default_retry(),
|
|
||||||
)
|
|
||||||
self.session.headers.update({"Authorization": f"Api-Key {api_key}"})
|
self.session.headers.update({"Authorization": f"Api-Key {api_key}"})
|
||||||
|
|
||||||
def get_info(self, **kwargs) -> Response:
|
def get_info(self, **kwargs) -> Response:
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
[tool.isort]
|
|
||||||
profile = "black"
|
|
||||||
combine_as_imports = true
|
|
||||||
known_first_party = ["libretime_api_client"]
|
|
||||||
|
|
||||||
[tool.pylint.messages_control]
|
[tool.pylint.messages_control]
|
||||||
extension-pkg-whitelist = "pydantic"
|
extension-pkg-whitelist = "pydantic"
|
||||||
disable = [
|
disable = [
|
||||||
|
@ -11,13 +6,13 @@ disable = [
|
||||||
"missing-module-docstring",
|
"missing-module-docstring",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[tool.pylint.format]
|
||||||
|
disable = "logging-fstring-interpolation"
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
log_cli = true
|
log_cli = true
|
||||||
log_cli_level = "DEBUG"
|
log_cli_level = "DEBUG"
|
||||||
|
|
||||||
[tool.coverage.run]
|
|
||||||
source = ["libretime_api_client"]
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["setuptools", "wheel"]
|
requires = ["setuptools", "wheel"]
|
||||||
build-backend = "setuptools.build_meta"
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Please do not edit this file, edit the setup.py file!
|
# Please do not edit this file, edit the setup.py file!
|
||||||
# This file is auto-generated by tools/extract_requirements.py.
|
# This file is auto-generated by tools/extract_requirements.py.
|
||||||
python-dateutil>=2.8.1,<2.10
|
python-dateutil>=2.8.1,<2.9
|
||||||
requests>=2.32.2,<2.33
|
requests>=2.25.1,<2.29
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
from setuptools import find_packages, setup
|
from setuptools import find_packages, setup
|
||||||
|
|
||||||
version = "4.2.0" # x-release-please-version
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="libretime-api-client",
|
name="libretime-api-client",
|
||||||
version=version,
|
version="3.0.0-beta.2",
|
||||||
description="LibreTime API Client",
|
description="LibreTime API Client",
|
||||||
author="LibreTime Contributors",
|
author="LibreTime Contributors",
|
||||||
url="https://github.com/libretime/libretime",
|
url="https://github.com/libretime/libretime",
|
||||||
|
@ -16,16 +14,16 @@ setup(
|
||||||
license="AGPLv3",
|
license="AGPLv3",
|
||||||
packages=find_packages(exclude=["*tests*", "*fixtures*"]),
|
packages=find_packages(exclude=["*tests*", "*fixtures*"]),
|
||||||
package_data={"": ["py.typed"]},
|
package_data={"": ["py.typed"]},
|
||||||
python_requires=">=3.8",
|
python_requires=">=3.6",
|
||||||
install_requires=[
|
install_requires=[
|
||||||
"python-dateutil>=2.8.1,<2.10",
|
"python-dateutil>=2.8.1,<2.9",
|
||||||
"requests>=2.32.2,<2.33",
|
"requests>=2.25.1,<2.29",
|
||||||
],
|
],
|
||||||
extras_require={
|
extras_require={
|
||||||
"dev": [
|
"dev": [
|
||||||
"requests-mock>=1.10.0,<2",
|
"requests-mock",
|
||||||
"types-python-dateutil>=2.8.1,<3",
|
"types-python-dateutil",
|
||||||
"types-requests>=2.31.0,<3",
|
"types-requests",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from libretime_api_client._utils import (
|
||||||
|
ApcUrl,
|
||||||
|
ApiRequest,
|
||||||
|
IncompleteUrl,
|
||||||
|
RequestProvider,
|
||||||
|
UrlBadParam,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"url, params, expected",
|
||||||
|
[
|
||||||
|
("one/two/three", {}, "one/two/three"),
|
||||||
|
("/testing/{key}", {"key": "aaa"}, "/testing/aaa"),
|
||||||
|
(
|
||||||
|
"/more/{key_a}/{key_b}/testing",
|
||||||
|
{"key_a": "aaa", "key_b": "bbb"},
|
||||||
|
"/more/aaa/bbb/testing",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_apc_url(url: str, params: dict, expected: str):
|
||||||
|
found = ApcUrl(url)
|
||||||
|
assert found.base_url == url
|
||||||
|
assert found.params(**params).url() == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_apc_url_bad_param():
|
||||||
|
url = ApcUrl("/testing/{key}")
|
||||||
|
with pytest.raises(UrlBadParam):
|
||||||
|
url.params(bad_key="testing")
|
||||||
|
|
||||||
|
|
||||||
|
def test_apc_url_incomplete():
|
||||||
|
url = ApcUrl("/{one}/{two}/three").params(two="testing")
|
||||||
|
with pytest.raises(IncompleteUrl):
|
||||||
|
url.url()
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_request_init():
|
||||||
|
req = ApiRequest("request_name", ApcUrl("/test/ing"))
|
||||||
|
assert req.name == "request_name"
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_request_call_json():
|
||||||
|
return_value = {"ok": "ok"}
|
||||||
|
|
||||||
|
read = MagicMock()
|
||||||
|
read.headers = {"content-type": "application/json"}
|
||||||
|
read.json = MagicMock(return_value=return_value)
|
||||||
|
|
||||||
|
with patch("requests.get") as mock_method:
|
||||||
|
mock_method.return_value = read
|
||||||
|
request = ApiRequest("mm", ApcUrl("http://localhost/testing"))()
|
||||||
|
assert request == return_value
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_request_call_html():
|
||||||
|
return_value = "<html><head></head><body></body></html>"
|
||||||
|
|
||||||
|
read = MagicMock()
|
||||||
|
read.headers = {"content-type": "application/html"}
|
||||||
|
read.text = MagicMock(return_value=return_value)
|
||||||
|
|
||||||
|
with patch("requests.get") as mock_method:
|
||||||
|
mock_method.return_value = read
|
||||||
|
request = ApiRequest("mm", ApcUrl("http://localhost/testing"))()
|
||||||
|
assert request.text() == return_value
|
||||||
|
|
||||||
|
|
||||||
|
def test_request_provider_init():
|
||||||
|
request_provider = RequestProvider(
|
||||||
|
base_url="http://localhost/test",
|
||||||
|
api_key="test_key",
|
||||||
|
endpoints={},
|
||||||
|
)
|
||||||
|
assert len(request_provider.available_requests()) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_request_provider_contains():
|
||||||
|
endpoints = {
|
||||||
|
"upload_recorded": "/1/",
|
||||||
|
}
|
||||||
|
request_provider = RequestProvider(
|
||||||
|
base_url="http://localhost/test",
|
||||||
|
api_key="test_key",
|
||||||
|
endpoints=endpoints,
|
||||||
|
)
|
||||||
|
|
||||||
|
for endpoint in endpoints:
|
||||||
|
assert endpoint in request_provider.requests
|
|
@ -1,21 +0,0 @@
|
||||||
import pytest
|
|
||||||
|
|
||||||
from libretime_api_client.v1 import ApiClient
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"base_url",
|
|
||||||
[
|
|
||||||
("http://localhost:8080"),
|
|
||||||
("http://localhost:8080/base"),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def test_api_client(requests_mock, base_url):
|
|
||||||
api_client = ApiClient(base_url=base_url, api_key="test-key")
|
|
||||||
|
|
||||||
requests_mock.get(
|
|
||||||
f"{base_url}/api/version",
|
|
||||||
json={"api_version": "1.0.0"},
|
|
||||||
)
|
|
||||||
|
|
||||||
assert api_client.version() == "1.0.0"
|
|
22
api/Makefile
22
api/Makefile
|
@ -4,22 +4,32 @@ include ../tools/python.mk
|
||||||
|
|
||||||
PIP_INSTALL := \
|
PIP_INSTALL := \
|
||||||
--editable ../shared \
|
--editable ../shared \
|
||||||
--editable .[dev,sentry]
|
--editable .[dev]
|
||||||
PYLINT_ARG := libretime_api
|
PYLINT_ARG := libretime_api
|
||||||
MYPY_ARG := libretime_api
|
MYPY_ARG := libretime_api
|
||||||
BANDIT_ARG := --exclude '*/tests/*' libretime_api || true
|
BANDIT_ARG := --exclude '*/tests/*' libretime_api || true
|
||||||
|
|
||||||
export DJANGO_SETTINGS_MODULE=libretime_api.settings.testing
|
|
||||||
|
|
||||||
format: .format
|
format: .format
|
||||||
lint: .format-check .pylint .mypy .bandit
|
lint: .format-check .pylint .mypy .bandit
|
||||||
test: .pytest
|
|
||||||
test-coverage: .coverage
|
|
||||||
clean: .clean
|
clean: .clean
|
||||||
|
|
||||||
|
test: $(VENV)
|
||||||
|
source $(VENV)/bin/activate
|
||||||
|
export DJANGO_SETTINGS_MODULE=libretime_api.settings.testing
|
||||||
|
pytest -v \
|
||||||
|
--numprocesses=$(CPU_CORES) \
|
||||||
|
--color=yes \
|
||||||
|
--cov-config=pyproject.toml \
|
||||||
|
--cov-report=term \
|
||||||
|
--cov-report=xml:./coverage.xml \
|
||||||
|
--cov=libretime_api \
|
||||||
|
libretime_api
|
||||||
|
|
||||||
SCHEMA_FILE ?= schema.yml
|
SCHEMA_FILE ?= schema.yml
|
||||||
schema: $(VENV)
|
schema: $(VENV)
|
||||||
$(VENV)/bin/libretime-api spectacular --file $(SCHEMA_FILE)
|
source $(VENV)/bin/activate
|
||||||
|
export DJANGO_SETTINGS_MODULE=libretime_api.settings.testing
|
||||||
|
libretime-api spectacular --file $(SCHEMA_FILE)
|
||||||
if command -v npx > /dev/null; then npx prettier --write $(SCHEMA_FILE); fi
|
if command -v npx > /dev/null; then npx prettier --write $(SCHEMA_FILE); fi
|
||||||
|
|
||||||
schema-foreach-commit:
|
schema-foreach-commit:
|
||||||
|
|
|
@ -11,6 +11,7 @@ PrivateTmp=true
|
||||||
PrivateUsers=true
|
PrivateUsers=true
|
||||||
ProtectClock=true
|
ProtectClock=true
|
||||||
ProtectControlGroups=true
|
ProtectControlGroups=true
|
||||||
|
ProtectHome=true
|
||||||
ProtectHostname=true
|
ProtectHostname=true
|
||||||
ProtectKernelLogs=true
|
ProtectKernelLogs=true
|
||||||
ProtectKernelModules=true
|
ProtectKernelModules=true
|
||||||
|
@ -18,15 +19,14 @@ ProtectKernelTunables=true
|
||||||
ProtectProc=invisible
|
ProtectProc=invisible
|
||||||
ProtectSystem=full
|
ProtectSystem=full
|
||||||
|
|
||||||
Environment=PYTHONOPTIMIZE=2
|
|
||||||
Environment=LIBRETIME_CONFIG_FILEPATH=@@CONFIG_FILEPATH@@
|
Environment=LIBRETIME_CONFIG_FILEPATH=@@CONFIG_FILEPATH@@
|
||||||
Environment=LIBRETIME_LOG_FILEPATH=@@LOG_DIR@@/api.log
|
Environment=LIBRETIME_LOG_FILEPATH=@@LOG_DIR@@/api.log
|
||||||
|
|
||||||
Type=notify
|
Type=notify
|
||||||
KillMode=mixed
|
KillMode=mixed
|
||||||
ExecStart=@@VENV_DIR@@/bin/gunicorn \
|
ExecStart=/usr/bin/gunicorn \
|
||||||
--workers 4 \
|
--workers 4 \
|
||||||
--worker-class libretime_api.gunicorn.Worker \
|
--worker-class uvicorn.workers.UvicornWorker \
|
||||||
--log-file - \
|
--log-file - \
|
||||||
--bind unix:/run/libretime-api.sock \
|
--bind unix:/run/libretime-api.sock \
|
||||||
libretime_api.asgi
|
libretime_api.asgi
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
from importlib.metadata import version as get_version
|
|
||||||
|
|
||||||
PACKAGE = __name__
|
|
||||||
VERSION = get_version(__name__)
|
|
|
@ -18,13 +18,12 @@ class StreamPreferences(BaseModel):
|
||||||
input_fade_transition: float
|
input_fade_transition: float
|
||||||
message_format: MessageFormatKind
|
message_format: MessageFormatKind
|
||||||
message_offline: str
|
message_offline: str
|
||||||
replay_gain_enabled: bool
|
|
||||||
replay_gain_offset: float
|
|
||||||
|
|
||||||
# input_auto_switch_off: bool
|
# input_auto_switch_off: bool
|
||||||
# input_auto_switch_on: bool
|
# input_auto_switch_on: bool
|
||||||
# input_main_user: str
|
# input_main_user: str
|
||||||
# input_main_password: str
|
# input_main_password: str
|
||||||
|
# replay_gain_enabled: bool
|
||||||
|
# replay_gain_offset: float
|
||||||
# track_fade_in: float
|
# track_fade_in: float
|
||||||
# track_fade_out: float
|
# track_fade_out: float
|
||||||
# track_fade_transition: float
|
# track_fade_transition: float
|
||||||
|
@ -79,12 +78,8 @@ class Preference(models.Model):
|
||||||
entries = dict(cls.site.values_list("key", "value"))
|
entries = dict(cls.site.values_list("key", "value"))
|
||||||
return StreamPreferences(
|
return StreamPreferences(
|
||||||
input_fade_transition=float(entries.get("default_transition_fade") or 0.0),
|
input_fade_transition=float(entries.get("default_transition_fade") or 0.0),
|
||||||
message_format=MessageFormatKind(
|
message_format=int(entries.get("stream_label_format") or 0),
|
||||||
int(entries.get("stream_label_format") or 0)
|
|
||||||
),
|
|
||||||
message_offline=entries.get("off_air_meta") or "Offline",
|
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
|
@classmethod
|
||||||
|
|
|
@ -8,7 +8,6 @@ from .role import Role
|
||||||
|
|
||||||
|
|
||||||
class UserManager(BaseUserManager):
|
class UserManager(BaseUserManager):
|
||||||
# pylint: disable=too-many-positional-arguments
|
|
||||||
def create_user(self, role, username, password, email, first_name, last_name):
|
def create_user(self, role, username, password, email, first_name, last_name):
|
||||||
user = self.model(
|
user = self.model(
|
||||||
role=role,
|
role=role,
|
||||||
|
@ -21,7 +20,6 @@ class UserManager(BaseUserManager):
|
||||||
user.save(using=self._db)
|
user.save(using=self._db)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
# pylint: disable=too-many-positional-arguments
|
|
||||||
def create_superuser(self, username, password, email, first_name, last_name):
|
def create_superuser(self, username, password, email, first_name, last_name):
|
||||||
return self.create_user(
|
return self.create_user(
|
||||||
Role.ADMIN,
|
Role.ADMIN,
|
||||||
|
|
|
@ -6,8 +6,6 @@ class StreamPreferencesSerializer(serializers.Serializer):
|
||||||
input_fade_transition = serializers.FloatField(read_only=True)
|
input_fade_transition = serializers.FloatField(read_only=True)
|
||||||
message_format = serializers.IntegerField(read_only=True)
|
message_format = serializers.IntegerField(read_only=True)
|
||||||
message_offline = serializers.CharField(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
|
# pylint: disable=abstract-method
|
||||||
|
|
|
@ -4,7 +4,7 @@ from libretime_api.core.models.preference import Preference
|
||||||
# pylint: disable=invalid-name,unused-argument
|
# pylint: disable=invalid-name,unused-argument
|
||||||
def test_preference_get_site_preferences(db):
|
def test_preference_get_site_preferences(db):
|
||||||
result = Preference.get_site_preferences()
|
result = Preference.get_site_preferences()
|
||||||
assert result.model_dump() == {
|
assert result.dict() == {
|
||||||
"station_name": "LibreTime",
|
"station_name": "LibreTime",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,19 +12,17 @@ def test_preference_get_site_preferences(db):
|
||||||
# pylint: disable=invalid-name,unused-argument
|
# pylint: disable=invalid-name,unused-argument
|
||||||
def test_preference_get_stream_preferences(db):
|
def test_preference_get_stream_preferences(db):
|
||||||
result = Preference.get_stream_preferences()
|
result = Preference.get_stream_preferences()
|
||||||
assert result.model_dump() == {
|
assert result.dict() == {
|
||||||
"input_fade_transition": 0.0,
|
"input_fade_transition": 0.0,
|
||||||
"message_format": 0,
|
"message_format": 0,
|
||||||
"message_offline": "LibreTime - offline",
|
"message_offline": "LibreTime - offline",
|
||||||
"replay_gain_enabled": True,
|
|
||||||
"replay_gain_offset": 0.0,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=invalid-name,unused-argument
|
# pylint: disable=invalid-name,unused-argument
|
||||||
def test_preference_get_stream_state(db):
|
def test_preference_get_stream_state(db):
|
||||||
result = Preference.get_stream_state()
|
result = Preference.get_stream_state()
|
||||||
assert result.model_dump() == {
|
assert result.dict() == {
|
||||||
"input_main_connected": False,
|
"input_main_connected": False,
|
||||||
"input_main_streaming": False,
|
"input_main_streaming": False,
|
||||||
"input_show_connected": False,
|
"input_show_connected": False,
|
||||||
|
|
|
@ -9,8 +9,6 @@ def test_stream_preferences_get(db, api_client: APIClient):
|
||||||
"input_fade_transition": 0.0,
|
"input_fade_transition": 0.0,
|
||||||
"message_format": 0,
|
"message_format": 0,
|
||||||
"message_offline": "LibreTime - offline",
|
"message_offline": "LibreTime - offline",
|
||||||
"replay_gain_enabled": True,
|
|
||||||
"replay_gain_offset": 0.0,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ class InfoView(APIView):
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
data = Preference.get_site_preferences()
|
data = Preference.get_site_preferences()
|
||||||
return Response(
|
return Response(
|
||||||
data.model_dump(
|
data.dict(
|
||||||
include={
|
include={
|
||||||
"station_name",
|
"station_name",
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,13 +14,11 @@ class StreamPreferencesView(views.APIView):
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
data = Preference.get_stream_preferences()
|
data = Preference.get_stream_preferences()
|
||||||
return Response(
|
return Response(
|
||||||
data.model_dump(
|
data.dict(
|
||||||
include={
|
include={
|
||||||
"input_fade_transition",
|
"input_fade_transition",
|
||||||
"message_format",
|
"message_format",
|
||||||
"message_offline",
|
"message_offline",
|
||||||
"replay_gain_enabled",
|
|
||||||
"replay_gain_offset",
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -34,7 +32,7 @@ class StreamStateView(views.APIView):
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
data = Preference.get_stream_state()
|
data = Preference.get_stream_state()
|
||||||
return Response(
|
return Response(
|
||||||
data.model_dump(
|
data.dict(
|
||||||
include={
|
include={
|
||||||
"input_main_connected",
|
"input_main_connected",
|
||||||
"input_main_streaming",
|
"input_main_streaming",
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
from uvicorn.workers import UvicornWorker # pylint: disable=import-error
|
|
||||||
|
|
||||||
|
|
||||||
class Worker(UvicornWorker):
|
|
||||||
CONFIG_KWARGS = {"lifespan": "off"}
|
|
|
@ -8,6 +8,8 @@ UP = """
|
||||||
-- DELETE FROM cc_pref WHERE keystr = 'system_version';
|
-- DELETE FROM cc_pref WHERE keystr = 'system_version';
|
||||||
-- INSERT INTO cc_pref (keystr, valstr) VALUES ('system_version', '2.5.5');
|
-- 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
|
DOWN = None
|
||||||
|
|
|
@ -5,7 +5,7 @@ from django.db import migrations
|
||||||
from ._migrations import legacy_migration_factory
|
from ._migrations import legacy_migration_factory
|
||||||
|
|
||||||
UP = """
|
UP = """
|
||||||
ALTER TABLE cc_files ADD COLUMN artwork VARCHAR(255);
|
ALTER TABLE cc_files ADD COLUMN artwork TYPE character varying(255);
|
||||||
"""
|
"""
|
||||||
|
|
||||||
DOWN = None
|
DOWN = None
|
||||||
|
|
|
@ -4,18 +4,12 @@ from django.db import migrations
|
||||||
|
|
||||||
from ._migrations import legacy_migration_factory
|
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 = """
|
UP = """
|
||||||
|
ALTER TABLE cc_files ADD COLUMN artwork VARCHAR(4096);
|
||||||
"""
|
"""
|
||||||
|
|
||||||
DOWN = """
|
DOWN = """
|
||||||
|
ALTER TABLE cc_files DROP COLUMN IF EXISTS artwork;
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
# pylint: disable=invalid-name
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
from ._migrations import legacy_migration_factory
|
|
||||||
|
|
||||||
UP = """
|
|
||||||
drop table if exists "cc_stream_setting" cascade;
|
|
||||||
"""
|
|
||||||
|
|
||||||
DOWN = """
|
|
||||||
create table "cc_stream_setting"
|
|
||||||
(
|
|
||||||
"keyname" varchar(64) not null,
|
|
||||||
"value" varchar(255),
|
|
||||||
"type" varchar(16) not null,
|
|
||||||
primary key ("keyname")
|
|
||||||
);
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("legacy", "0040_bump_legacy_schema_version"),
|
|
||||||
]
|
|
||||||
operations = [
|
|
||||||
migrations.RunPython(
|
|
||||||
code=legacy_migration_factory(
|
|
||||||
target="41",
|
|
||||||
sql=UP,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
]
|
|
|
@ -1,46 +0,0 @@
|
||||||
# pylint: disable=invalid-name
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
from ._migrations import legacy_migration_factory
|
|
||||||
|
|
||||||
UP = """
|
|
||||||
delete from cc_pref
|
|
||||||
where "keystr" in (
|
|
||||||
'default_icecast_password',
|
|
||||||
'default_stream_mount_point',
|
|
||||||
'live_dj_connection_url_override',
|
|
||||||
'live_dj_source_connection_url',
|
|
||||||
'master_dj_connection_url_override',
|
|
||||||
'master_dj_source_connection_url',
|
|
||||||
'max_bitrate',
|
|
||||||
'num_of_streams',
|
|
||||||
'stream_bitrate',
|
|
||||||
'stream_type'
|
|
||||||
);
|
|
||||||
"""
|
|
||||||
|
|
||||||
DOWN = """
|
|
||||||
insert into
|
|
||||||
cc_pref ("keystr", "valstr")
|
|
||||||
values
|
|
||||||
('default_stream_mount_point', 'main'),
|
|
||||||
('max_bitrate', '320'),
|
|
||||||
('num_of_streams', '3'),
|
|
||||||
('stream_bitrate', '24, 32, 48, 64, 96, 128, 160, 192, 224, 256, 320'),
|
|
||||||
('stream_type', 'ogg, mp3, opus, aac');
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("legacy", "0041_drop_stream_setting_table"),
|
|
||||||
]
|
|
||||||
operations = [
|
|
||||||
migrations.RunPython(
|
|
||||||
code=legacy_migration_factory(
|
|
||||||
target="42",
|
|
||||||
sql=UP,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
]
|
|
|
@ -1,26 +0,0 @@
|
||||||
# pylint: disable=invalid-name
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
from ._migrations import legacy_migration_factory
|
|
||||||
|
|
||||||
UP = """
|
|
||||||
delete from cc_pref
|
|
||||||
where "keystr" = 'allowed_cors_urls';
|
|
||||||
"""
|
|
||||||
|
|
||||||
DOWN = """"""
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("legacy", "0042_remove_stream_preferences"),
|
|
||||||
]
|
|
||||||
operations = [
|
|
||||||
migrations.RunPython(
|
|
||||||
code=legacy_migration_factory(
|
|
||||||
target="43",
|
|
||||||
sql=UP,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
]
|
|
|
@ -1,29 +0,0 @@
|
||||||
# pylint: disable=invalid-name
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
from ._migrations import legacy_migration_factory
|
|
||||||
|
|
||||||
UP = """
|
|
||||||
alter table "cc_track_types" add column "analyze_cue_points" boolean default 'f' not null;
|
|
||||||
|
|
||||||
update "cc_track_types" set "analyze_cue_points" = 't';
|
|
||||||
"""
|
|
||||||
|
|
||||||
DOWN = """
|
|
||||||
alter table "cc_track_types" drop column if exists "analyze_cue_points";
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("legacy", "0043_remove_cors_preference"),
|
|
||||||
]
|
|
||||||
operations = [
|
|
||||||
migrations.RunPython(
|
|
||||||
code=legacy_migration_factory(
|
|
||||||
target="44",
|
|
||||||
sql=UP,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
]
|
|
|
@ -1,34 +0,0 @@
|
||||||
# pylint: disable=invalid-name
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
from ._migrations import legacy_migration_factory
|
|
||||||
|
|
||||||
UP = """
|
|
||||||
CREATE TABLE "sessions"
|
|
||||||
(
|
|
||||||
"id" CHAR(32) NOT NULL,
|
|
||||||
"modified" INTEGER,
|
|
||||||
"lifetime" INTEGER,
|
|
||||||
"data" TEXT,
|
|
||||||
PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
"""
|
|
||||||
|
|
||||||
DOWN = """
|
|
||||||
DROP TABLE IF EXISTS "sessions" CASCADE;
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("legacy", "0044_add_track_types_analyzer_options"),
|
|
||||||
]
|
|
||||||
operations = [
|
|
||||||
migrations.RunPython(
|
|
||||||
code=legacy_migration_factory(
|
|
||||||
target="45",
|
|
||||||
sql=UP,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
]
|
|
|
@ -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,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
]
|
|
|
@ -1,2 +1 @@
|
||||||
# The schema version is defined using the migration file prefix number
|
LEGACY_SCHEMA_VERSION = "3.0.0-beta.0.1"
|
||||||
LEGACY_SCHEMA_VERSION = "46"
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue