Merge pull request #1210 from jooola/setup-pre-commit

Add pre-commit setup
This commit is contained in:
Kyle Robbertze 2021-06-07 23:14:56 +02:00 committed by GitHub
commit e8d5481422
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
227 changed files with 17420 additions and 16313 deletions

10
.codespellignore Normal file
View File

@ -0,0 +1,10 @@
hda
HDA
conexant
# TODO: See https://github.com/savonet/liquidsoap/issues/1654
prefered
# TODO: Remove once docs/lunr.js is shipped using a package manager
ment
enviroments

View File

@ -1,10 +1,9 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
title: ""
labels: bug
assignees: ''
assignees: ""
---
**Describe the bug**
@ -12,6 +11,7 @@ A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
@ -24,18 +24,20 @@ A clear and concise description of what you expected to happen.
Version from the upgrade popup if you can reach it.
**Installation method:**
- OS: [e.g. Ubuntu]
- OS Version [e.g. 16.04.5 LTS (Xenial Xerus)]
- Method: [e.g. `./install` script or packages]
- Details: [how did you call the install script, where did you get packages from]
- OS: [e.g. Ubuntu]
- OS Version [e.g. 16.04.5 LTS (Xenial Xerus)]
- Method: [e.g. `./install` script or packages]
- Details: [how did you call the install script, where did you get packages from]
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Client (please complete the following information if applicable):**
- OS: [e.g. Fedora]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
- OS: [e.g. Fedora]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@ -1,10 +1,9 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
title: ""
labels: feature-request
assignees: ''
assignees: ""
---
**Is your feature request related to a problem? Please describe.**

10
.github/RELEASE.md vendored
View File

@ -21,7 +21,7 @@ Please report new issues and/or feature requests in the issue tracker. Join our
Liquidsoap support
No watched folder support
No Line In recording support
Playout wont work if locale is missing
Playout won't work if locale is missing
Lack of i18n toolchain is disturbing
## Features
@ -86,8 +86,8 @@ storage_backend=file
You can then remove the files and the symlink.
rm /etc/airtime/cloud_storage.conf \
/etc/airtime/rabbitmq-analyzer.ini \
/etc/airtime/production
/etc/airtime/rabbitmq-analyzer.ini \
/etc/airtime/production
While you're at you may also want to remove the amazon_s3 section if it was in any of the files.
@ -155,7 +155,7 @@ Currently LibreTime does not support watching folders. Uploading files through t
### No line in support
This feature went missing from LibreTime due to the fact that we based our code off of the saas-dev branch of legacy upstream and support for recording hasn't been ported to the new airtime analyzer ingest system. #42 currently tracks the progress being made on line in recording.
Playout wont work if locale is missing
Playout won't work if locale is missing
Some minimal OS installs do not have a default locale configured. This only seems to affect some VPS installs as they often do not have a locale setup in the default images provided.
@ -174,4 +174,4 @@ sudo update-locale LANGUAGE="en_US.UTF-8"
### Lack of i18n toolchain is disturbing
Some translations might miss the tarball. They didn't get lost, but the build chain needs fixing. Work is in #301 and additional work is needed as it has become clear that we probably want to support bidirectional translation syncing with zanata.
Some translations might miss the tarball. They didn't get lost, but the build chain needs fixing. Work is in #301 and additional work is needed as it has become clear that we probably want to support bidirectional translation syncing with zanata.

1
.github/lock.yml vendored
View File

@ -25,6 +25,5 @@ lockComment: >
# Assign `resolved` as the reason for locking. Set to `false` to disable
setLockReason: true
# Limit to only `issues` or `pulls`
# only: issues

View File

@ -1,4 +1,4 @@
#/bin/bash
#!/usr/bin/env bash
# Adding repos and packages
add-apt-repository -y ppa:libretime/libretime

View File

@ -1,4 +1,4 @@
#/bin/bash
#!/usr/bin/env bash
# Adding repos and packages
add-apt-repository -y ppa:libretime/libretime

View File

@ -1,8 +1,9 @@
#/bin/bash
#!/usr/bin/env bash
echo "::group::Install Python apps"
pip3 install nose mock
for app in `ls python_apps`; do
for app in $(ls python_apps); do
pip3 install -e python_apps/$app
done
echo "::endgroup::"

View File

@ -1,4 +1,4 @@
#/bin/bash
#!/usr/bin/env bash
failed='f'
# Starting at repo root

View File

@ -1,4 +1,4 @@
#/bin/bash
#!/usr/bin/env bash
#release.sh 1.8.2
#creates a libretime folder with a "1.8.2" suffix

2
.github/stale.yml vendored
View File

@ -45,7 +45,7 @@ markComment: >
# Comment to post when closing a stale Issue or Pull Request.
closeComment: >
This issue has been autmatically closed after is was marked as stale and
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

View File

@ -2,14 +2,29 @@ name: Python and PHP Tests
on:
push:
paths-ignore:
- 'docs/**'
- "docs/**"
pull_request:
types: [opened, ready_for_review, review_requested, edited, reopened, synchronize]
types:
[
opened,
ready_for_review,
review_requested,
edited,
reopened,
synchronize,
]
paths-ignore:
- 'docs/**'
- "docs/**"
workflow_dispatch:
jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- uses: pre-commit/action@v2.0.3
test-bionic:
runs-on: ubuntu-18.04
env:
@ -19,7 +34,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: '3.6'
python-version: "3.6"
- name: Setup PostgreSQL
run: |
sudo systemctl start postgresql.service
@ -31,10 +46,9 @@ jobs:
- name: Setup PHP with specific version
uses: shivammathur/setup-php@v2
with:
php-version: '7.2'
php-version: "7.2"
- name: Install prerequisites
run:
sudo -E ./.github/scripts/install-bionic.sh
run: sudo -E ./.github/scripts/install-bionic.sh
- name: Run Python tests
run: |
sudo ./.github/scripts/python-pkg-install.sh
@ -62,7 +76,7 @@ jobs:
- name: Setup PHP with specific version
uses: shivammathur/setup-php@v2
with:
php-version: '7.0'
php-version: "7.0"
- name: Install prerequisites
run: sudo -E ./.github/scripts/install-xenial.sh
- name: Run PHP tests

58
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,58 @@
---
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
exclude: ^(airtime_mvc.*)$
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.1
hooks:
- id: check-added-large-files
- id: check-case-conflict
- id: check-executables-have-shebangs
- id: check-shebang-scripts-are-executable
- id: check-symlinks
- id: destroyed-symlinks
- id: check-json
- id: check-yaml
- id: check-yaml
- id: check-toml
- id: check-merge-conflict
- id: end-of-file-fixer
- id: mixed-line-ending
args: [--fix=lf]
- id: trailing-whitespace
- id: requirements-txt-fixer
- id: name-tests-test
# TODO: Remove once the django api uses pytest
exclude: ^(api.*)$
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v2.3.0
hooks:
- id: prettier
files: \.(md|yml|yaml|json)$
- repo: https://github.com/psf/black
rev: 21.5b1
hooks:
- id: black
- repo: https://github.com/pycqa/isort
rev: 5.8.0
hooks:
- id: isort
args: ["--profile", "black", "--filter-files"]
# - repo: https://github.com/pre-commit/mirrors-pylint
# rev: v3.0.0a3
# hooks:
# - id: pylint
- repo: https://github.com/codespell-project/codespell
rev: v2.0.0
hooks:
- id: codespell
args: [--ignore-words=.codespellignore]

View File

@ -1,33 +1,48 @@
# Contributing to LibreTime
First and foremost, thank you! We appreciate that you want to
contribute to LibreTime, your time is valuable, and your
contribute to LibreTime, your time is valuable, and your
contributions mean a lot to us.
**What does "contributing" mean?**
Creating an issue is the simplest form of contributing to a
project. But there are many ways to contribute, including
project. But there are many ways to contribute, including
the following:
* Updating or correcting documentation
* Feature requests
* Bug reports
- Updating or correcting documentation
- Feature requests
- Bug reports
Before opening an issue, please:
* read and be prepared to adhere to our [code of conduct](https://github.com/LibreTime/code-of-conduct/blob/master/CODE_OF_CONDUCT.md)
* understand that we follow the standardized [C4 development process](https://rfc.zeromq.org/spec:42/C4/)
* [search for existing duplicate or closed issues](https://github.com/LibreTime/libretime/issues?utf8=%E2%9C%93&q=is%3Aissue)
* clearly state the problem you would like to solve in a meaningful way
* be prepared to follow up on issues by providing additional information as requested by a maintainer or contributor helping you out
- read and be prepared to adhere to our [code of conduct](https://github.com/LibreTime/code-of-conduct/blob/master/CODE_OF_CONDUCT.md)
- understand that we follow the standardized [C4 development process](https://rfc.zeromq.org/spec:42/C4/)
- [search for existing duplicate or closed issues](https://github.com/LibreTime/libretime/issues?utf8=%E2%9C%93&q=is%3Aissue)
- clearly state the problem you would like to solve in a meaningful way
- be prepared to follow up on issues by providing additional information as requested by a maintainer or contributor helping you out
For bug reports, please provide the following details:
* **version**: what version of LibreTime you were using when you experienced the bug?
* **distro**: what distribution is your install on and which distro version are you using (ie. Ubuntu Trusty)
* **reduced test case**: the minimum amount of detail needed to reproduce the bug
* **error messages**: please paste any error reports into the issue or a gist
- **version**: what version of LibreTime you were using when you experienced the bug?
- **distro**: what distribution is your install on and which distro version are you using (ie. Ubuntu Trusty)
- **reduced test case**: the minimum amount of detail needed to reproduce the bug
- **error messages**: please paste any error reports into the issue or a gist
Please wrap all code and error messages in [markdown code
Please wrap all code and error messages in [markdown code
fences](https://help.github.com/articles/creating-and-highlighting-code-blocks/).
### Contributing code
To make sure that you don't accidentally commit code that does not follow the coding style, you can
install a [`pre-commit`](https://pre-commit.com/) hook that will check that everything is in order:
```bash
pre-commit install
```
You can also run it anytime using:
```bash
pre-commit run --all-files
```

88
CREDITS
View File

@ -92,7 +92,7 @@ Martin Konecny (martin.konecny@sourcefabric.org)
James Moon (james.moon@sourcefabric.org)
Role: Software Developer
Denise Rigato (denise.rigato@sourcefabric.org)
Role: Software Developer
@ -124,7 +124,7 @@ Martin Konecny (martin.konecny@sourcefabric.org)
James Moon (james.moon@sourcefabric.org)
Role: Software Developer
Denise Rigato (denise.rigato@sourcefabric.org)
Role: Software Developer
@ -147,7 +147,7 @@ Naomi Aro (naomi.aro@sourcefabric.org)
James Moon (james.moon@sourcefabric.org)
Role: Software Developer
Denise Rigato (denise.rigato@sourcefabric.org)
Role: Software Developer
@ -198,7 +198,7 @@ Naomi Aro (naomi.aro@sourcefabric.org)
James Moon (james.moon@sourcefabric.org)
Role: Software Developer
Denise Rigato (denise.rigato@sourcefabric.org)
Role: Software Developer
@ -267,7 +267,7 @@ Martin Konecny (martin.konecny@sourcefabric.org)
James Moon (james.moon@sourcefabric.org)
Role: Software Developer
Yuchen Wang (yuchen.wang@sourcefabric.org)
Role: Software Developer
@ -350,10 +350,10 @@ Version 1.6.1
-------------
Same as previous version.
Version 1.6.0
Version 1.6.0
-------------
This version marks a major change to the project, completely replacing the
custom audio player with liquidsoap, dropping the custom desktop GUI, and
custom audio player with liquidsoap, dropping the custom desktop GUI, and
completely rewriting the web interface. The project has also been renamed
from "Campcaster" to "Airtime" for this release.
@ -361,11 +361,11 @@ Paul Baranowski (paul.baranowski@sourcefabric.org)
Role: Project Lead / Software Developer
Highlights:
- Integration and development of liquidsoap scheduler
- Separation of playlists from the scheduler
- Separation of playlists from the scheduler
Naomi Aro (naomi.aro@sourcefabric.org)
Role: Software Developer
Highlights:
Highlights:
- New User Interface
- Conversion to Propel DB backend
@ -397,44 +397,44 @@ generated radio station based in Basel, Switzerland powered by Campcaster. We ar
very grateful for their contributions, and specifically to Thomas Gilgen, Dirk Claes,
Rigzen Latshang and Fabiano Sidler.
Douglas Arellanes
Douglas Arellanes
- Tester and user feedback
Robin Gareus
Robin Gareus
- Packaging
Ferenc Gerlits
Ferenc Gerlits
- Studio GUI
Sebastian Göbel
Sebastian Göbel
- Web interface, storage server
Nebojsa Grujic
Nebojsa Grujic
- Scheduler, XML-RPC interface, Gstreamer plugins
Tomáš Hlava
Tomáš Hlava
- Bug fixes
Sava Tatić
Sava Tatić
- Manager
Version 1.3.0 - "Dakar"
-----------------------
Douglas Arellanes
Douglas Arellanes
- Tester and user feedback
Ferenc Gerlits
Ferenc Gerlits
- Studio GUI, scheduler, packaging
Sebastian Göbel
Sebastian Göbel
- Web interface
Tomáš Hlava
Tomáš Hlava
- Bug fixes
Sava Tatić
Sava Tatić
- Manager
@ -442,19 +442,19 @@ Version 1.2.0 - "Kotor"
-----------------------
In alphabetical order:
Douglas Arellanes
Douglas Arellanes
- Tester and user feedback
Paul Baranowski
Paul Baranowski
- Project manager, HTML UI, storage server
Ferenc Gerlits
Ferenc Gerlits
- Studio GUI, scheduler, packaging
Tomáš Hlava
Tomáš Hlava
- Bug fixes
Robert Klajn
- Superuser feedback
Mark Kretschmann
Robert Klajn
- Superuser feedback
Mark Kretschmann
- Audio player
Sava Tatić
Sava Tatić
- Manager
@ -462,40 +462,40 @@ Version 1.1.X - "Freetown"
--------------------------
In alphabetical order:
Douglas Arellanes
Douglas Arellanes
- Tester and user feedback
Paul Baranowski
Paul Baranowski
- Project manager, HTML UI, storage server, scheduler
János Csikós
János Csikós
- HTML UI
Ferenc Gerlits
Ferenc Gerlits
- Studio GUI, scheduler, packaging
Tomáš Hlava
Tomáš Hlava
- Storage server, network hub
Mark Kretschmann
Mark Kretschmann
- Audio player
Ákos Maróy
Ákos Maróy
- Architecture design, scheduler, audio player
Sava Tatić
- Manager
Version 1.0
-----------
The original Campcaster (LiveSupport) concept was drafted by Micz Flor. It was
fully developed by Robert Klajn, Douglas Arellanes, Ákos Maróy, and Sava Tatić.
The user interface has been designed by Charles Truett, based on the initial work
done by a team of his then-fellow Parsons School of Design students Turi McKinley,
Catalin Lazia and Sangita Shah. The team was led by then-head of the school's
The original Campcaster (LiveSupport) concept was drafted by Micz Flor. It was
fully developed by Robert Klajn, Douglas Arellanes, Ákos Maróy, and Sava Tatić.
The user interface has been designed by Charles Truett, based on the initial work
done by a team of his then-fellow Parsons School of Design students Turi McKinley,
Catalin Lazia and Sangita Shah. The team was led by then-head of the school's
Department of Digital Design Colleen Macklin, assisted by Kunal Jain.
In alphabetical order:
Douglas Arellanes
Michael Aschauer
Micz Flor
Michael Aschauer
Micz Flor
Ferenc Gerlits
Sebastian Göbel
Tomáš Hlava
Nadine Kokot
Ákos Maróy
Ákos Maróy
Sava Tatić
Charles Truett

View File

@ -15,7 +15,7 @@ We are currently ramping up development on this repository.
Check out the [documentation](http://libretime.org) for more information and
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/code-of-conduct/blob/master/CODE_OF_CONDUCT.md).
By participating in this project you agree to abide by its terms.
@ -23,12 +23,12 @@ Please submit enhancements, bugfixes or comments via GitHub.
## Development Process
The LibreTime follows the standardized [Collective Code Construction
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
> 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
@ -100,7 +100,7 @@ your organization. Your logo will show up here with a link to your website.
LibreTime is free software: you can redistribute it and/or
modify it under the terms of the GNU Affero General Public
License as published by the Free Software Foundation,
License as published by the Free Software Foundation,
version 3 of the License.
## Copyright
@ -109,6 +109,6 @@ Copyright (c) 2011-2017 Sourcefabric z.ú.
Copyright (c) 2017 LibreTime Community
Please refer to the original [README](README),
[CREDITS](CREDITS) and [LICENSE_3RD_PARTY](LICENSE_3RD_PARTY)
Please refer to the original [README](README),
[CREDITS](CREDITS) and [LICENSE_3RD_PARTY](LICENSE_3RD_PARTY)
for more information.

View File

@ -1,7 +1,7 @@
<style type="text/css">
#plupload_files input[type="file"] {
font-size: 200px !important;
}
#plupload_files input[type="file"] {
font-size: 200px !important;
}
</style>
<script type="text/javascript">
var LIBRETIME_PLUPLOAD_MAX_FILE_SIZE = "<?php echo $this->uploadMaxSize; ?>";
@ -11,15 +11,15 @@
$partitions = Application_Model_Systemstatus::GetDiskInfo();
$status = new StdClass;
$disk = $partitions[0];
$used = $disk->totalSpace-$disk->totalFreeSpace;
$used = $disk->totalSpace - $disk->totalFreeSpace;
$total = $disk->totalSpace;
$tracktypes = Application_Model_Tracktype::getTracktypes();
array_multisort(array_map(function($element) {
array_multisort(array_map(function ($element) {
return $element['type_name'];
}, $tracktypes), SORT_ASC, $tracktypes);
if(count($tracktypes) == 0) {
if (count($tracktypes) == 0) {
$hasTracktypes = "disabled";
$showTracktypesDropdown = false;
} else {
@ -28,35 +28,35 @@
}
?>
<?php
if (isset($_COOKIE['tt_upload'])) {
$ttsaved = $_COOKIE['tt_upload'];
} else {
// Use default track type
$ttsaved = Application_Model_Preference::GetTrackTypeDefault();
}
?>
if (isset($_COOKIE['tt_upload'])) {
$ttsaved = $_COOKIE['tt_upload'];
} else {
// Use default track type
$ttsaved = Application_Model_Preference::GetTrackTypeDefault();
}
?>
<div id="upload_wrapper">
<div id="track_type_selection">
<form>
<?php
if ($showTracktypesDropdown != false) { ?>
<select id="select_type" class="form-control" <?php echo $hasTracktypes; ?>>
<?php
echo "<option value=''>Select Track Type</option>";
foreach ($tracktypes as $key => $tt) {
$selected = "";
if ($ttsaved == $tt['code']) {
$selected = "selected";
<?php
if ($showTracktypesDropdown != false) { ?>
<select id="select_type" class="form-control" <?php echo $hasTracktypes; ?>>
<?php
echo "<option value=''>Select Track Type</option>";
foreach ($tracktypes as $key => $tt) {
$selected = "";
if ($ttsaved == $tt['code']) {
$selected = "selected";
}
$code = $tt['code'];
$typename = $tt['type_name'];
echo "<option value='$code' $selected>$typename</option>";
}
$code = $tt['code'];
$typename = $tt['type_name'];
echo "<option value='$code' $selected>$typename</option>";
}
?>
</select>
<?php } ?>
?>
</select>
<?php } ?>
</form>
</div>
@ -65,13 +65,13 @@
$ttTitle = "";
foreach ($tracktypes as $key => $tt) {
if ($ttsaved == $tt['code']) {
$ttTitle = $tt['type_name'];
$ttTitle = $tt['type_name'];
}
}
}
?>
<H2><?php echo _("Upload")?> <span id="upload_type" <?php echo ($showTracktypesDropdown && $ttTitle!="") ? 'style="color:#ff611f"' : "" ?>>
<?php echo ($showTracktypesDropdown && $ttTitle!="") ? $ttTitle : "Tracks"; ?></span></H2>
<H2><?php echo _("Upload") ?> <span id="upload_type" <?php echo ($showTracktypesDropdown && $ttTitle != "") ? 'style="color:#ff611f"' : "" ?>>
<?php echo ($showTracktypesDropdown && $ttTitle != "") ? $ttTitle : "Tracks"; ?></span></H2>
<form action="/rest/media" method="post" id="add-media-dropzone" class="dropzone dz-clickable">
<?php echo $this->form->getElement('csrf') ?>
<div class="dz-message">
@ -81,39 +81,38 @@
<!--
<div id="filelist">Your browser doesn't have Flash, Silverlight or HTML5 support.</div>
<br />
 
<div id="container">
    <a id="pickfiles" href="javascript:;">[Select files]</a>
    <a id="uploadfiles" href="javascript:;">[Upload files]</a>
<a id="pickfiles" href="javascript:;">[Select files]</a>
<a id="uploadfiles" href="javascript:;">[Upload files]</a>
</div>
-->
<div id="uploads_disk_usage">
<div style="padding-bottom: 2px;"><?php echo _("Storage")?></div>
<div style="padding-bottom: 2px;"><?php echo _("Storage") ?></div>
<div class="disk_usage_progress_bar"></div>
<div class="disk_usage_percent_in_use"><?php echo sprintf("%01.1f%% ", $used/$total*100) . _("in use") ?></div>
<div class="disk_usage_used" style="width:<?php echo sprintf("%01.1f%%", min(100, $used/$total*100)) ?>;"></div>
<div class="disk_usage_percent_in_use"><?php echo sprintf("%01.1f%% ", $used / $total * 100) . _("in use") ?></div>
<div class="disk_usage_used" style="width:<?php echo sprintf("%01.1f%%", min(100, $used / $total * 100)) ?>;"></div>
<div style="margin-top: 17px; font-size: 12px;"><?php echo sprintf(_("%01.1fGB of %01.1fGB"), $used/pow(2, 30), $total/pow(2, 30)); ?></div>
<div style="margin-top: 17px; font-size: 12px;"><?php echo sprintf(_("%01.1fGB of %01.1fGB"), $used / pow(2, 30), $total / pow(2, 30)); ?></div>
</div>
</div>
</div>
<div id="plupload_error">
<table></table>
<table></table>
</div>
<div id="recent_uploads_wrapper" class="lib-content ui-widget ui-widget-content block-shadow content-pane wide-panel">
<div id="recent_uploads" class="outer-datatable-wrapper padded">
<div id="recent_uploads_filter">
<form>
<input type="radio" name="upload_status" id="upload_status_all" checked /><label for="upload_status_all"><?php echo _("All")?></label>
<input type="radio" name="upload_status" id="upload_status_failed" /><label for="upload_status_failed"><?php echo _("Failed")?></label>
<input type="radio" name="upload_status" id="upload_status_pending" /><label for="upload_status_pending"><?php echo _("Pending")?></label>
<input type="radio" name="upload_status" id="upload_status_all" checked /><label for="upload_status_all"><?php echo _("All") ?></label>
<input type="radio" name="upload_status" id="upload_status_failed" /><label for="upload_status_failed"><?php echo _("Failed") ?></label>
<input type="radio" name="upload_status" id="upload_status_pending" /><label for="upload_status_pending"><?php echo _("Pending") ?></label>
</form>
</div>
<H2><?php echo _("Recent Uploads")?></H2>
<table id="recent_uploads_table" class="datatable lib-content ui-widget ui-widget-content block-shadow"
cellpadding="0" cellspacing="0"></table>
<H2><?php echo _("Recent Uploads") ?></H2>
<table id="recent_uploads_table" class="datatable lib-content ui-widget ui-widget-content block-shadow" cellpadding="0" cellspacing="0"></table>
</div>
<div style="clear: both;"></div>
</div>
</div>

View File

@ -5656,7 +5656,7 @@ msgstr "Impossible de se connecter au serveur RabbitMQ! Veuillez vérifier si le
#~ msgid ""
#~ "Our new Dashboard view now has a powerful tabbed editing interface, so updating your tracks and playlists\n"
#~ " is easier than ever."
#~ msgstr "Notre nouvelle vue Tableau de bord dispose désormais dune puissante interface dédition par onglets. La mise à jour de vos pistes et de vos listes de lecture est donc plus simple que jamais."
#~ msgstr "Notre nouvelle vue Tableau de bord dispose désormais d'une puissante interface d'édition par onglets. La mise à jour de vos pistes et de vos listes de lecture est donc plus simple que jamais."
#~ msgid ""
#~ "We've streamlined the Airtime interface to make navigation easier. With the most important tools\n"

View File

@ -1832,11 +1832,11 @@
*/
var splitDateTime = function(dateFormat, dateTimeString, dateSettings, timeSettings) {
try {
// The idea is to get the number separator occurances in datetime and the time format requested (since time has
// The idea is to get the number separator occurrences in datetime and the time format requested (since time has
// fewer unknowns, mostly numbers and am/pm). We will use the time pattern to split.
var separator = timeSettings && timeSettings.separator ? timeSettings.separator : $.datetimepicker._defaults.separator,
format = timeSettings && timeSettings.timeFormat ? timeSettings.timeFormat : $.datetimepicker._defaults.timeFormat,
timeParts = format.split(separator), // how many occurances of separator may be in our format?
timeParts = format.split(separator), // how many occurrences of separator may be in our format?
timePartsLen = timeParts.length,
allParts = dateTimeString.split(separator),
allPartsLen = allParts.length;

View File

@ -4,6 +4,7 @@ This API provides access to LibreTime's database via a Django application. This
API supersedes the [PHP API](../airtime_mvc/application/controllers/ApiController.php).
## Deploying
Deploying in a production environment is done in the [`install`](../install)
script which installs LibreTime. This is how the API is installed in the Vagrant
development images too. This method does not automatically reflect changes to
@ -18,6 +19,7 @@ Endpoint exploration and documentation is available from
instance.
### Development
For a live reloading version within Vagrant:
```
@ -38,6 +40,7 @@ sudo -u www-data LIBRETIME_DEBUG=True python3 bin/libretime-api test libretimeap
```
## 3rd Party Licences
`libretimeapi/tests/resources/song.mp3`: Steps - Tears On The Dancefloor (Album
Teaser) by mceyedol. Downloaded from
https://soundcloud.com/mceyedol/steps-tears-on-the-dancefloor-album-teaser

5
api/bin/libretime-api Normal file → Executable file
View File

@ -1,11 +1,12 @@
#!/usr/bin/env python3
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'libretimeapi.settings')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "libretimeapi.settings")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
@ -17,5 +18,5 @@ def main():
execute_from_command_line(sys.argv)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View File

@ -1,7 +1,8 @@
from django.apps import AppConfig
from django.db.models.signals import pre_save
class LibreTimeAPIConfig(AppConfig):
name = 'libretimeapi'
verbose_name = 'LibreTime API'
default_auto_field = 'django.db.models.AutoField'
name = "libretimeapi"
verbose_name = "LibreTime API"
default_auto_field = "django.db.models.AutoField"

View File

@ -1,20 +1,22 @@
from django.contrib.auth.models import BaseUserManager
class UserManager(BaseUserManager):
def create_user(self, username, type, email, first_name, last_name, password):
user = self.model(username=username,
type=type,
email=email,
first_name=first_name,
last_name=last_name)
user = self.model(
username=username,
type=type,
email=email,
first_name=first_name,
last_name=last_name,
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, username, email, first_name, last_name, password):
user = self.create_user(username, 'A', email, first_name, last_name, password)
user = self.create_user(username, "A", email, first_name, last_name, password)
return user
def get_by_natural_key(self, username):
return self.get(username=username)

View File

@ -1,11 +1,13 @@
import hashlib
from django.contrib import auth
from django.contrib.auth.models import AbstractBaseUser, Permission
from django.core.exceptions import PermissionDenied
from django.db import models
from libretimeapi.managers import UserManager
from libretimeapi.permission_constants import GROUPS
from .user_constants import USER_TYPES, ADMIN
from .user_constants import ADMIN, USER_TYPES
class LoginAttempt(models.Model):
@ -14,18 +16,20 @@ class LoginAttempt(models.Model):
class Meta:
managed = False
db_table = 'cc_login_attempts'
db_table = "cc_login_attempts"
class Session(models.Model):
sessid = models.CharField(primary_key=True, max_length=32)
userid = models.ForeignKey('User', models.DO_NOTHING, db_column='userid', blank=True, null=True)
userid = models.ForeignKey(
"User", models.DO_NOTHING, db_column="userid", blank=True, null=True
)
login = models.CharField(max_length=255, blank=True, null=True)
ts = models.DateTimeField(blank=True, null=True)
class Meta:
managed = False
db_table = 'cc_sess'
db_table = "cc_sess"
USER_TYPE_CHOICES = ()
@ -34,12 +38,14 @@ for item in USER_TYPES.items():
class User(AbstractBaseUser):
username = models.CharField(db_column='login', unique=True, max_length=255)
password = models.CharField(db_column='pass', max_length=255) # Field renamed because it was a Python reserved word.
username = models.CharField(db_column="login", unique=True, max_length=255)
password = models.CharField(
db_column="pass", max_length=255
) # Field renamed because it was a Python reserved word.
type = models.CharField(max_length=1, choices=USER_TYPE_CHOICES)
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
last_login = models.DateTimeField(db_column='lastlogin', blank=True, null=True)
last_login = models.DateTimeField(db_column="lastlogin", blank=True, null=True)
lastfail = models.DateTimeField(blank=True, null=True)
skype_contact = models.CharField(max_length=1024, blank=True, null=True)
jabber_contact = models.CharField(max_length=1024, blank=True, null=True)
@ -47,13 +53,13 @@ class User(AbstractBaseUser):
cell_phone = models.CharField(max_length=1024, blank=True, null=True)
login_attempts = models.IntegerField(blank=True, null=True)
USERNAME_FIELD = 'username'
EMAIL_FIELD = 'email'
REQUIRED_FIELDS = ['type', 'email', 'first_name', 'last_name']
USERNAME_FIELD = "username"
EMAIL_FIELD = "email"
REQUIRED_FIELDS = ["type", "email", "first_name", "last_name"]
objects = UserManager()
def get_full_name(self):
return '{} {}'.format(self.first_name, self.last_name)
return "{} {}".format(self.first_name, self.last_name)
def get_short_name(self):
return self.first_name
@ -65,7 +71,7 @@ class User(AbstractBaseUser):
self.password = hashlib.md5(password.encode()).hexdigest()
def is_staff(self):
print('is_staff')
print("is_staff")
return self.type == ADMIN
def check_password(self, password):
@ -81,6 +87,7 @@ class User(AbstractBaseUser):
(managed = True), then this can be replaced with
django.contrib.auth.models.PermissionMixin.
"""
def is_superuser(self):
return self.type == ADMIN
@ -124,7 +131,7 @@ class User(AbstractBaseUser):
class Meta:
managed = False
db_table = 'cc_subjs'
db_table = "cc_subjs"
class UserToken(models.Model):
@ -138,4 +145,4 @@ class UserToken(models.Model):
class Meta:
managed = False
db_table = 'cc_subjs_token'
db_table = "cc_subjs_token"

View File

@ -3,11 +3,13 @@ from django.db import models
class CeleryTask(models.Model):
task_id = models.CharField(max_length=256)
track_reference = models.ForeignKey('ThirdPartyTrackReference', models.DO_NOTHING, db_column='track_reference')
track_reference = models.ForeignKey(
"ThirdPartyTrackReference", models.DO_NOTHING, db_column="track_reference"
)
name = models.CharField(max_length=256, blank=True, null=True)
dispatch_time = models.DateTimeField(blank=True, null=True)
status = models.CharField(max_length=256)
class Meta:
managed = False
db_table = 'celery_tasks'
db_table = "celery_tasks"

View File

@ -7,5 +7,4 @@ class Country(models.Model):
class Meta:
managed = False
db_table = 'cc_country'
db_table = "cc_country"

View File

@ -5,11 +5,20 @@ class File(models.Model):
name = models.CharField(max_length=255)
mime = models.CharField(max_length=255)
ftype = models.CharField(max_length=128)
directory = models.ForeignKey('MusicDir', models.DO_NOTHING, db_column='directory', blank=True, null=True)
directory = models.ForeignKey(
"MusicDir", models.DO_NOTHING, db_column="directory", blank=True, null=True
)
filepath = models.TextField(blank=True, null=True)
import_status = models.IntegerField()
currently_accessing = models.IntegerField(db_column='currentlyaccessing')
edited_by = models.ForeignKey('User', models.DO_NOTHING, db_column='editedby', blank=True, null=True, related_name='edited_files')
currently_accessing = models.IntegerField(db_column="currentlyaccessing")
edited_by = models.ForeignKey(
"User",
models.DO_NOTHING,
db_column="editedby",
blank=True,
null=True,
related_name="edited_files",
)
mtime = models.DateTimeField(blank=True, null=True)
utime = models.DateTimeField(blank=True, null=True)
lptime = models.DateTimeField(blank=True, null=True)
@ -58,8 +67,10 @@ class File(models.Model):
contributor = models.CharField(max_length=512, blank=True, null=True)
language = models.CharField(max_length=512, blank=True, null=True)
file_exists = models.BooleanField(blank=True, null=True)
replay_gain = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True)
owner = models.ForeignKey('User', models.DO_NOTHING, blank=True, null=True)
replay_gain = models.DecimalField(
max_digits=8, decimal_places=2, blank=True, null=True
)
owner = models.ForeignKey("User", models.DO_NOTHING, blank=True, null=True)
cuein = models.DurationField(blank=True, null=True)
cueout = models.DurationField(blank=True, null=True)
silan_check = models.BooleanField(blank=True, null=True)
@ -76,10 +87,10 @@ class File(models.Model):
class Meta:
managed = False
db_table = 'cc_files'
db_table = "cc_files"
permissions = [
('change_own_file', 'Change the files where they are the owner'),
('delete_own_file', 'Delete the files where they are the owner'),
("change_own_file", "Change the files where they are the owner"),
("delete_own_file", "Delete the files where they are the owner"),
]
@ -91,15 +102,16 @@ class MusicDir(models.Model):
class Meta:
managed = False
db_table = 'cc_music_dirs'
db_table = "cc_music_dirs"
class CloudFile(models.Model):
storage_backend = models.CharField(max_length=512)
resource_id = models.TextField()
filename = models.ForeignKey(File, models.DO_NOTHING, blank=True, null=True,
db_column='cc_file_id')
filename = models.ForeignKey(
File, models.DO_NOTHING, blank=True, null=True, db_column="cc_file_id"
)
class Meta:
managed = False
db_table = 'cloud_file'
db_table = "cloud_file"

View File

@ -1,4 +1,5 @@
from django.db import models
from .files import File
from .smart_blocks import SmartBlock
@ -7,7 +8,7 @@ class Playlist(models.Model):
name = models.CharField(max_length=255)
mtime = models.DateTimeField(blank=True, null=True)
utime = models.DateTimeField(blank=True, null=True)
creator = models.ForeignKey('User', models.DO_NOTHING, blank=True, null=True)
creator = models.ForeignKey("User", models.DO_NOTHING, blank=True, null=True)
description = models.CharField(max_length=512, blank=True, null=True)
length = models.DurationField(blank=True, null=True)
@ -16,7 +17,7 @@ class Playlist(models.Model):
class Meta:
managed = False
db_table = 'cc_playlist'
db_table = "cc_playlist"
class PlaylistContent(models.Model):
@ -38,4 +39,4 @@ class PlaylistContent(models.Model):
class Meta:
managed = False
db_table = 'cc_playlistcontents'
db_table = "cc_playlistcontents"

View File

@ -1,15 +1,16 @@
from django.db import models
from .files import File
class ListenerCount(models.Model):
timestamp = models.ForeignKey('Timestamp', models.DO_NOTHING)
mount_name = models.ForeignKey('MountName', models.DO_NOTHING)
timestamp = models.ForeignKey("Timestamp", models.DO_NOTHING)
mount_name = models.ForeignKey("MountName", models.DO_NOTHING)
listener_count = models.IntegerField()
class Meta:
managed = False
db_table = 'cc_listener_count'
db_table = "cc_listener_count"
class LiveLog(models.Model):
@ -19,18 +20,20 @@ class LiveLog(models.Model):
class Meta:
managed = False
db_table = 'cc_live_log'
db_table = "cc_live_log"
class PlayoutHistory(models.Model):
file = models.ForeignKey(File, models.DO_NOTHING, blank=True, null=True)
starts = models.DateTimeField()
ends = models.DateTimeField(blank=True, null=True)
instance = models.ForeignKey('ShowInstance', models.DO_NOTHING, blank=True, null=True)
instance = models.ForeignKey(
"ShowInstance", models.DO_NOTHING, blank=True, null=True
)
class Meta:
managed = False
db_table = 'cc_playout_history'
db_table = "cc_playout_history"
class PlayoutHistoryMetadata(models.Model):
@ -40,7 +43,7 @@ class PlayoutHistoryMetadata(models.Model):
class Meta:
managed = False
db_table = 'cc_playout_history_metadata'
db_table = "cc_playout_history_metadata"
class PlayoutHistoryTemplate(models.Model):
@ -49,7 +52,7 @@ class PlayoutHistoryTemplate(models.Model):
class Meta:
managed = False
db_table = 'cc_playout_history_template'
db_table = "cc_playout_history_template"
class PlayoutHistoryTemplateField(models.Model):
@ -62,7 +65,7 @@ class PlayoutHistoryTemplateField(models.Model):
class Meta:
managed = False
db_table = 'cc_playout_history_template_field'
db_table = "cc_playout_history_template_field"
class Timestamp(models.Model):
@ -70,4 +73,4 @@ class Timestamp(models.Model):
class Meta:
managed = False
db_table = 'cc_timestamp'
db_table = "cc_timestamp"

View File

@ -1,4 +1,5 @@
from django.db import models
from .authentication import User
from .files import File
@ -7,14 +8,14 @@ class ImportedPodcast(models.Model):
auto_ingest = models.BooleanField()
auto_ingest_timestamp = models.DateTimeField(blank=True, null=True)
album_override = models.BooleanField()
podcast = models.ForeignKey('Podcast', models.DO_NOTHING)
podcast = models.ForeignKey("Podcast", models.DO_NOTHING)
def get_owner(self):
return self.podcast.owner
class Meta:
managed = False
db_table = 'imported_podcast'
db_table = "imported_podcast"
class Podcast(models.Model):
@ -31,17 +32,19 @@ class Podcast(models.Model):
itunes_subtitle = models.CharField(max_length=4096, blank=True, null=True)
itunes_category = models.CharField(max_length=4096, blank=True, null=True)
itunes_explicit = models.CharField(max_length=4096, blank=True, null=True)
owner = models.ForeignKey(User, models.DO_NOTHING, db_column='owner', blank=True, null=True)
owner = models.ForeignKey(
User, models.DO_NOTHING, db_column="owner", blank=True, null=True
)
def get_owner(self):
return self.owner
class Meta:
managed = False
db_table = 'podcast'
db_table = "podcast"
permissions = [
('change_own_podcast', 'Change the podcasts where they are the owner'),
('delete_own_podcast', 'Delete the podcasts where they are the owner'),
("change_own_podcast", "Change the podcasts where they are the owner"),
("delete_own_podcast", "Delete the podcasts where they are the owner"),
]
@ -59,10 +62,16 @@ class PodcastEpisode(models.Model):
class Meta:
managed = False
db_table = 'podcast_episodes'
db_table = "podcast_episodes"
permissions = [
('change_own_podcastepisode', 'Change the episodes of podcasts where they are the owner'),
('delete_own_podcastepisode', 'Delete the episodes of podcasts where they are the owner'),
(
"change_own_podcastepisode",
"Change the episodes of podcasts where they are the owner",
),
(
"delete_own_podcastepisode",
"Delete the episodes of podcasts where they are the owner",
),
]
@ -74,4 +83,4 @@ class StationPodcast(models.Model):
class Meta:
managed = False
db_table = 'station_podcast'
db_table = "station_podcast"

View File

@ -2,14 +2,16 @@ from django.db import models
class Preference(models.Model):
subjid = models.ForeignKey('User', models.DO_NOTHING, db_column='subjid', blank=True, null=True)
subjid = models.ForeignKey(
"User", models.DO_NOTHING, db_column="subjid", blank=True, null=True
)
keystr = models.CharField(unique=True, max_length=255, blank=True, null=True)
valstr = models.TextField(blank=True, null=True)
class Meta:
managed = False
db_table = 'cc_pref'
unique_together = (('subjid', 'keystr'),)
db_table = "cc_pref"
unique_together = (("subjid", "keystr"),)
class MountName(models.Model):
@ -17,7 +19,7 @@ class MountName(models.Model):
class Meta:
managed = False
db_table = 'cc_mount_name'
db_table = "cc_mount_name"
class StreamSetting(models.Model):
@ -27,4 +29,4 @@ class StreamSetting(models.Model):
class Meta:
managed = False
db_table = 'cc_stream_setting'
db_table = "cc_stream_setting"

View File

@ -1,4 +1,5 @@
from django.db import models
from .files import File
@ -6,14 +7,14 @@ class Schedule(models.Model):
starts = models.DateTimeField()
ends = models.DateTimeField()
file = models.ForeignKey(File, models.DO_NOTHING, blank=True, null=True)
stream = models.ForeignKey('Webstream', models.DO_NOTHING, blank=True, null=True)
stream = models.ForeignKey("Webstream", models.DO_NOTHING, blank=True, null=True)
clip_length = models.DurationField(blank=True, null=True)
fade_in = models.TimeField(blank=True, null=True)
fade_out = models.TimeField(blank=True, null=True)
cue_in = models.DurationField()
cue_out = models.DurationField()
media_item_played = models.BooleanField(blank=True, null=True)
instance = models.ForeignKey('ShowInstance', models.DO_NOTHING)
instance = models.ForeignKey("ShowInstance", models.DO_NOTHING)
playout_status = models.SmallIntegerField()
broadcasted = models.SmallIntegerField()
position = models.IntegerField()
@ -23,8 +24,8 @@ class Schedule(models.Model):
class Meta:
managed = False
db_table = 'cc_schedule'
db_table = "cc_schedule"
permissions = [
('change_own_schedule', 'Change the content on their shows'),
('delete_own_schedule', 'Delete the content on their shows'),
("change_own_schedule", "Change the content on their shows"),
("delete_own_schedule", "Delete the content on their shows"),
]

View File

@ -7,5 +7,4 @@ class ServiceRegister(models.Model):
class Meta:
managed = False
db_table = 'cc_service_register'
db_table = "cc_service_register"

View File

@ -1,6 +1,7 @@
from django.db import models
from .playlists import Playlist
from .files import File
from .playlists import Playlist
class Show(models.Model):
@ -26,7 +27,7 @@ class Show(models.Model):
class Meta:
managed = False
db_table = 'cc_show'
db_table = "cc_show"
class ShowDays(models.Model):
@ -46,16 +47,16 @@ class ShowDays(models.Model):
class Meta:
managed = False
db_table = 'cc_show_days'
db_table = "cc_show_days"
class ShowHost(models.Model):
show = models.ForeignKey(Show, models.DO_NOTHING)
subjs = models.ForeignKey('User', models.DO_NOTHING)
subjs = models.ForeignKey("User", models.DO_NOTHING)
class Meta:
managed = False
db_table = 'cc_show_hosts'
db_table = "cc_show_hosts"
class ShowInstance(models.Model):
@ -65,7 +66,7 @@ class ShowInstance(models.Model):
show = models.ForeignKey(Show, models.DO_NOTHING)
record = models.SmallIntegerField(blank=True, null=True)
rebroadcast = models.SmallIntegerField(blank=True, null=True)
instance = models.ForeignKey('self', models.DO_NOTHING, blank=True, null=True)
instance = models.ForeignKey("self", models.DO_NOTHING, blank=True, null=True)
file = models.ForeignKey(File, models.DO_NOTHING, blank=True, null=True)
time_filled = models.DurationField(blank=True, null=True)
created = models.DateTimeField()
@ -78,7 +79,7 @@ class ShowInstance(models.Model):
class Meta:
managed = False
db_table = 'cc_show_instances'
db_table = "cc_show_instances"
class ShowRebroadcast(models.Model):
@ -91,4 +92,4 @@ class ShowRebroadcast(models.Model):
class Meta:
managed = False
db_table = 'cc_show_rebroadcast'
db_table = "cc_show_rebroadcast"

View File

@ -5,7 +5,7 @@ class SmartBlock(models.Model):
name = models.CharField(max_length=255)
mtime = models.DateTimeField(blank=True, null=True)
utime = models.DateTimeField(blank=True, null=True)
creator = models.ForeignKey('User', models.DO_NOTHING, blank=True, null=True)
creator = models.ForeignKey("User", models.DO_NOTHING, blank=True, null=True)
description = models.CharField(max_length=512, blank=True, null=True)
length = models.DurationField(blank=True, null=True)
type = models.CharField(max_length=7, blank=True, null=True)
@ -15,16 +15,22 @@ class SmartBlock(models.Model):
class Meta:
managed = False
db_table = 'cc_block'
db_table = "cc_block"
permissions = [
('change_own_smartblock', 'Change the smartblocks where they are the owner'),
('delete_own_smartblock', 'Delete the smartblocks where they are the owner'),
(
"change_own_smartblock",
"Change the smartblocks where they are the owner",
),
(
"delete_own_smartblock",
"Delete the smartblocks where they are the owner",
),
]
class SmartBlockContent(models.Model):
block = models.ForeignKey(SmartBlock, models.DO_NOTHING, blank=True, null=True)
file = models.ForeignKey('File', models.DO_NOTHING, blank=True, null=True)
file = models.ForeignKey("File", models.DO_NOTHING, blank=True, null=True)
position = models.IntegerField(blank=True, null=True)
trackoffset = models.FloatField()
cliplength = models.DurationField(blank=True, null=True)
@ -38,10 +44,16 @@ class SmartBlockContent(models.Model):
class Meta:
managed = False
db_table = 'cc_blockcontents'
db_table = "cc_blockcontents"
permissions = [
('change_own_smartblockcontent', 'Change the content of smartblocks where they are the owner'),
('delete_own_smartblockcontent', 'Delete the content of smartblocks where they are the owner'),
(
"change_own_smartblockcontent",
"Change the content of smartblocks where they are the owner",
),
(
"delete_own_smartblockcontent",
"Delete the content of smartblocks where they are the owner",
),
]
@ -58,9 +70,14 @@ class SmartBlockCriteria(models.Model):
class Meta:
managed = False
db_table = 'cc_blockcriteria'
db_table = "cc_blockcriteria"
permissions = [
('change_own_smartblockcriteria', 'Change the criteria of smartblocks where they are the owner'),
('delete_own_smartblockcriteria', 'Delete the criteria of smartblocks where they are the owner'),
(
"change_own_smartblockcriteria",
"Change the criteria of smartblocks where they are the owner",
),
(
"delete_own_smartblockcriteria",
"Delete the criteria of smartblocks where they are the owner",
),
]

View File

@ -1,4 +1,5 @@
from django.db import models
from .files import File
@ -11,7 +12,8 @@ class ThirdPartyTrackReference(models.Model):
class Meta:
managed = False
db_table = 'third_party_track_references'
db_table = "third_party_track_references"
class TrackType(models.Model):
code = models.CharField(max_length=16, unique=True)
@ -21,5 +23,4 @@ class TrackType(models.Model):
class Meta:
managed = False
db_table = 'cc_track_types'
db_table = "cc_track_types"

View File

@ -1,11 +1,11 @@
GUEST = 'G'
DJ = 'H'
PROGRAM_MANAGER = 'P'
ADMIN = 'A'
GUEST = "G"
DJ = "H"
PROGRAM_MANAGER = "P"
ADMIN = "A"
USER_TYPES = {
GUEST: 'Guest',
DJ: 'DJ',
PROGRAM_MANAGER: 'Program Manager',
ADMIN: 'Admin',
GUEST: "Guest",
DJ: "DJ",
PROGRAM_MANAGER: "Program Manager",
ADMIN: "Admin",
}

View File

@ -1,5 +1,6 @@
from django.db import models
from django.contrib.auth import get_user_model
from django.db import models
from .schedule import Schedule
@ -20,10 +21,10 @@ class Webstream(models.Model):
class Meta:
managed = False
db_table = 'cc_webstream'
db_table = "cc_webstream"
permissions = [
('change_own_webstream', 'Change the webstreams where they are the owner'),
('delete_own_webstream', 'Delete the webstreams where they are the owner'),
("change_own_webstream", "Change the webstreams where they are the owner"),
("delete_own_webstream", "Delete the webstreams where they are the owner"),
]
@ -37,4 +38,4 @@ class WebstreamMetadata(models.Model):
class Meta:
managed = False
db_table = 'cc_webstream_metadata'
db_table = "cc_webstream_metadata"

View File

@ -1,101 +1,106 @@
import logging
from django.contrib.auth.models import Group, Permission
from .models.user_constants import GUEST, DJ, PROGRAM_MANAGER, USER_TYPES
from .models.user_constants import DJ, GUEST, PROGRAM_MANAGER, USER_TYPES
logger = logging.getLogger(__name__)
GUEST_PERMISSIONS = ['view_schedule',
'view_show',
'view_showdays',
'view_showhost',
'view_showinstance',
'view_showrebroadcast',
'view_file',
'view_podcast',
'view_podcastepisode',
'view_playlist',
'view_playlistcontent',
'view_smartblock',
'view_smartblockcontent',
'view_smartblockcriteria',
'view_webstream',
'view_apiroot',
]
DJ_PERMISSIONS = GUEST_PERMISSIONS + ['add_file',
'add_podcast',
'add_podcastepisode',
'add_playlist',
'add_playlistcontent',
'add_smartblock',
'add_smartblockcontent',
'add_smartblockcriteria',
'add_webstream',
'change_own_schedule',
'change_own_file',
'change_own_podcast',
'change_own_podcastepisode',
'change_own_playlist',
'change_own_playlistcontent',
'change_own_smartblock',
'change_own_smartblockcontent',
'change_own_smartblockcriteria',
'change_own_webstream',
'delete_own_schedule',
'delete_own_file',
'delete_own_podcast',
'delete_own_podcastepisode',
'delete_own_playlist',
'delete_own_playlistcontent',
'delete_own_smartblock',
'delete_own_smartblockcontent',
'delete_own_smartblockcriteria',
'delete_own_webstream',
]
PROGRAM_MANAGER_PERMISSIONS = GUEST_PERMISSIONS + ['add_show',
'add_showdays',
'add_showhost',
'add_showinstance',
'add_showrebroadcast',
'add_file',
'add_podcast',
'add_podcastepisode',
'add_playlist',
'add_playlistcontent',
'add_smartblock',
'add_smartblockcontent',
'add_smartblockcriteria',
'add_webstream',
'change_schedule',
'change_show',
'change_showdays',
'change_showhost',
'change_showinstance',
'change_showrebroadcast',
'change_file',
'change_podcast',
'change_podcastepisode',
'change_playlist',
'change_playlistcontent',
'change_smartblock',
'change_smartblockcontent',
'change_smartblockcriteria',
'change_webstream',
'delete_schedule',
'delete_show',
'delete_showdays',
'delete_showhost',
'delete_showinstance',
'delete_showrebroadcast',
'delete_file',
'delete_podcast',
'delete_podcastepisode',
'delete_playlist',
'delete_playlistcontent',
'delete_smartblock',
'delete_smartblockcontent',
'delete_smartblockcriteria',
'delete_webstream',
]
GUEST_PERMISSIONS = [
"view_schedule",
"view_show",
"view_showdays",
"view_showhost",
"view_showinstance",
"view_showrebroadcast",
"view_file",
"view_podcast",
"view_podcastepisode",
"view_playlist",
"view_playlistcontent",
"view_smartblock",
"view_smartblockcontent",
"view_smartblockcriteria",
"view_webstream",
"view_apiroot",
]
DJ_PERMISSIONS = GUEST_PERMISSIONS + [
"add_file",
"add_podcast",
"add_podcastepisode",
"add_playlist",
"add_playlistcontent",
"add_smartblock",
"add_smartblockcontent",
"add_smartblockcriteria",
"add_webstream",
"change_own_schedule",
"change_own_file",
"change_own_podcast",
"change_own_podcastepisode",
"change_own_playlist",
"change_own_playlistcontent",
"change_own_smartblock",
"change_own_smartblockcontent",
"change_own_smartblockcriteria",
"change_own_webstream",
"delete_own_schedule",
"delete_own_file",
"delete_own_podcast",
"delete_own_podcastepisode",
"delete_own_playlist",
"delete_own_playlistcontent",
"delete_own_smartblock",
"delete_own_smartblockcontent",
"delete_own_smartblockcriteria",
"delete_own_webstream",
]
PROGRAM_MANAGER_PERMISSIONS = GUEST_PERMISSIONS + [
"add_show",
"add_showdays",
"add_showhost",
"add_showinstance",
"add_showrebroadcast",
"add_file",
"add_podcast",
"add_podcastepisode",
"add_playlist",
"add_playlistcontent",
"add_smartblock",
"add_smartblockcontent",
"add_smartblockcriteria",
"add_webstream",
"change_schedule",
"change_show",
"change_showdays",
"change_showhost",
"change_showinstance",
"change_showrebroadcast",
"change_file",
"change_podcast",
"change_podcastepisode",
"change_playlist",
"change_playlistcontent",
"change_smartblock",
"change_smartblockcontent",
"change_smartblockcriteria",
"change_webstream",
"delete_schedule",
"delete_show",
"delete_showdays",
"delete_showhost",
"delete_showinstance",
"delete_showrebroadcast",
"delete_file",
"delete_podcast",
"delete_podcastepisode",
"delete_playlist",
"delete_playlistcontent",
"delete_smartblock",
"delete_smartblockcontent",
"delete_smartblockcriteria",
"delete_webstream",
]
GROUPS = {
GUEST: GUEST_PERMISSIONS,

View File

@ -1,23 +1,25 @@
from rest_framework.permissions import BasePermission
from django.conf import settings
from rest_framework.permissions import BasePermission
from .models.user_constants import DJ
REQUEST_PERMISSION_TYPE_MAP = {
'GET': 'view',
'HEAD': 'view',
'OPTIONS': 'view',
'POST': 'change',
'PUT': 'change',
'DELETE': 'delete',
'PATCH': 'change',
"GET": "view",
"HEAD": "view",
"OPTIONS": "view",
"POST": "change",
"PUT": "change",
"DELETE": "delete",
"PATCH": "change",
}
def get_own_obj(request, view):
user = request.user
if user is None or user.type != DJ:
return ''
if request.method == 'GET':
return ''
return ""
if request.method == "GET":
return ""
qs = view.queryset.all()
try:
model_owners = []
@ -26,32 +28,34 @@ def get_own_obj(request, view):
if owner not in model_owners:
model_owners.append(owner)
if len(model_owners) == 1 and user in model_owners:
return 'own_'
return "own_"
except AttributeError:
return ''
return ''
return ""
return ""
def get_permission_for_view(request, view):
try:
permission_type = REQUEST_PERMISSION_TYPE_MAP[request.method]
if view.__class__.__name__ == 'APIRootView':
return '{}_apiroot'.format(permission_type)
if view.__class__.__name__ == "APIRootView":
return "{}_apiroot".format(permission_type)
model = view.model_permission_name
own_obj = get_own_obj(request, view)
return '{permission_type}_{own_obj}{model}'.format(permission_type=permission_type,
own_obj=own_obj,
model=model)
return "{permission_type}_{own_obj}{model}".format(
permission_type=permission_type, own_obj=own_obj, model=model
)
except AttributeError:
return None
def check_authorization_header(request):
auth_header = request.META.get('Authorization')
if not auth_header:
auth_header = request.META.get('HTTP_AUTHORIZATION', '')
if auth_header.startswith('Api-Key'):
def check_authorization_header(request):
auth_header = request.META.get("Authorization")
if not auth_header:
auth_header = request.META.get("HTTP_AUTHORIZATION", "")
if auth_header.startswith("Api-Key"):
token = auth_header.split()[1]
if token == settings.CONFIG.get('general', 'api_key'):
if token == settings.CONFIG.get("general", "api_key"):
return True
return False
@ -62,6 +66,7 @@ class IsAdminOrOwnUser(BasePermission):
Django's standard permission system. For details see
https://www.django-rest-framework.org/api-guide/permissions/#custom-permissions
"""
def has_permission(self, request, view):
if request.user.is_superuser():
return True
@ -83,6 +88,7 @@ class IsSystemTokenOrUser(BasePermission):
an API-Key header. All standard-users (i.e. not using the API-Key) have their
permissions checked against Django's standard permission system.
"""
def has_permission(self, request, view):
if request.user and request.user.is_authenticated:
perm = get_permission_for_view(request, view)
@ -90,7 +96,7 @@ class IsSystemTokenOrUser(BasePermission):
# model. This use-case allows users to view the base of the API
# explorer. Their assigned group permissions determine further access
# into the explorer.
if perm == 'view_apiroot':
if perm == "view_apiroot":
return True
return request.user.has_perm(perm)
return check_authorization_header(request)

View File

@ -1,265 +1,307 @@
from django.contrib.auth import get_user_model
from rest_framework import serializers
from .models import *
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = get_user_model()
fields = [
'item_url',
'username',
'type',
'first_name',
'last_name',
'lastfail',
'skype_contact',
'jabber_contact',
'email',
'cell_phone',
'login_attempts',
"item_url",
"username",
"type",
"first_name",
"last_name",
"lastfail",
"skype_contact",
"jabber_contact",
"email",
"cell_phone",
"login_attempts",
]
class SmartBlockSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = SmartBlock
fields = '__all__'
fields = "__all__"
class SmartBlockContentSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = SmartBlockContent
fields = '__all__'
fields = "__all__"
class SmartBlockCriteriaSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = SmartBlockCriteria
fields = '__all__'
fields = "__all__"
class CountrySerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Country
fields = '__all__'
fields = "__all__"
class FileSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = File
fields = '__all__'
fields = "__all__"
class ListenerCountSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ListenerCount
fields = '__all__'
fields = "__all__"
class LiveLogSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = LiveLog
fields = '__all__'
fields = "__all__"
class LoginAttemptSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = LoginAttempt
fields = '__all__'
fields = "__all__"
class MountNameSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = MountName
fields = '__all__'
fields = "__all__"
class MusicDirSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = MusicDir
fields = '__all__'
fields = "__all__"
class PlaylistSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Playlist
fields = '__all__'
fields = "__all__"
class PlaylistContentSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = PlaylistContent
fields = '__all__'
fields = "__all__"
class PlayoutHistorySerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = PlayoutHistory
fields = '__all__'
fields = "__all__"
class PlayoutHistoryMetadataSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = PlayoutHistoryMetadata
fields = '__all__'
fields = "__all__"
class PlayoutHistoryTemplateSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = PlayoutHistoryTemplate
fields = '__all__'
fields = "__all__"
class PlayoutHistoryTemplateFieldSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = PlayoutHistoryTemplateField
fields = '__all__'
fields = "__all__"
class PreferenceSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Preference
fields = '__all__'
fields = "__all__"
class ScheduleSerializer(serializers.HyperlinkedModelSerializer):
file_id = serializers.IntegerField(source='file.id', read_only=True)
stream_id = serializers.IntegerField(source='stream.id', read_only=True)
instance_id = serializers.IntegerField(source='instance.id', read_only=True)
file_id = serializers.IntegerField(source="file.id", read_only=True)
stream_id = serializers.IntegerField(source="stream.id", read_only=True)
instance_id = serializers.IntegerField(source="instance.id", read_only=True)
class Meta:
model = Schedule
fields = [
'item_url',
'id',
'starts',
'ends',
'clip_length',
'fade_in',
'fade_out',
'cue_in',
'cue_out',
'media_item_played',
'file',
'file_id',
'stream',
'stream_id',
'instance',
'instance_id',
"item_url",
"id",
"starts",
"ends",
"clip_length",
"fade_in",
"fade_out",
"cue_in",
"cue_out",
"media_item_played",
"file",
"file_id",
"stream",
"stream_id",
"instance",
"instance_id",
]
class ServiceRegisterSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ServiceRegister
fields = '__all__'
fields = "__all__"
class SessionSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Session
fields = '__all__'
fields = "__all__"
class ShowSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Show
fields = [
'item_url',
'id',
'name',
'url',
'genre',
'description',
'color',
'background_color',
'linked',
'is_linkable',
'image_path',
'has_autoplaylist',
'autoplaylist_repeat',
'autoplaylist',
"item_url",
"id",
"name",
"url",
"genre",
"description",
"color",
"background_color",
"linked",
"is_linkable",
"image_path",
"has_autoplaylist",
"autoplaylist_repeat",
"autoplaylist",
]
class ShowDaysSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ShowDays
fields = '__all__'
fields = "__all__"
class ShowHostSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ShowHost
fields = '__all__'
fields = "__all__"
class ShowInstanceSerializer(serializers.HyperlinkedModelSerializer):
show_id = serializers.IntegerField(source='show.id', read_only=True)
file_id = serializers.IntegerField(source='file.id', read_only=True)
show_id = serializers.IntegerField(source="show.id", read_only=True)
file_id = serializers.IntegerField(source="file.id", read_only=True)
class Meta:
model = ShowInstance
fields = [
'item_url',
'id',
'description',
'starts',
'ends',
'record',
'rebroadcast',
'time_filled',
'created',
'last_scheduled',
'modified_instance',
'autoplaylist_built',
'show',
'show_id',
'instance',
'file',
'file_id',
"item_url",
"id",
"description",
"starts",
"ends",
"record",
"rebroadcast",
"time_filled",
"created",
"last_scheduled",
"modified_instance",
"autoplaylist_built",
"show",
"show_id",
"instance",
"file",
"file_id",
]
class ShowRebroadcastSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ShowRebroadcast
fields = '__all__'
fields = "__all__"
class StreamSettingSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = StreamSetting
fields = '__all__'
fields = "__all__"
class UserTokenSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = UserToken
fields = '__all__'
fields = "__all__"
class TimestampSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Timestamp
fields = '__all__'
fields = "__all__"
class WebstreamSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Webstream
fields = '__all__'
fields = "__all__"
class WebstreamMetadataSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = WebstreamMetadata
fields = '__all__'
fields = "__all__"
class CeleryTaskSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = CeleryTask
fields = '__all__'
fields = "__all__"
class CloudFileSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = CloudFile
fields = '__all__'
fields = "__all__"
class ImportedPodcastSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ImportedPodcast
fields = '__all__'
fields = "__all__"
class PodcastSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Podcast
fields = '__all__'
fields = "__all__"
class PodcastEpisodeSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = PodcastEpisode
fields = '__all__'
fields = "__all__"
class StationPodcastSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = StationPodcast
fields = '__all__'
fields = "__all__"
class ThirdPartyTrackReferenceSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ThirdPartyTrackReference
fields = '__all__'
fields = "__all__"
class TrackTypeSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = TrackType
fields = '__all__'
fields = "__all__"

View File

@ -1,11 +1,13 @@
import configparser
import os
from .utils import read_config_file, get_random_string
LIBRETIME_CONF_DIR = os.getenv('LIBRETIME_CONF_DIR', '/etc/airtime')
DEFAULT_CONFIG_PATH = os.getenv('LIBRETIME_CONF_FILE',
os.path.join(LIBRETIME_CONF_DIR, 'airtime.conf'))
API_VERSION = '2.0.0'
from .utils import get_random_string, read_config_file
LIBRETIME_CONF_DIR = os.getenv("LIBRETIME_CONF_DIR", "/etc/airtime")
DEFAULT_CONFIG_PATH = os.getenv(
"LIBRETIME_CONF_FILE", os.path.join(LIBRETIME_CONF_DIR, "airtime.conf")
)
API_VERSION = "2.0.0"
try:
CONFIG = read_config_file(DEFAULT_CONFIG_PATH)
@ -17,70 +19,70 @@ except IOError:
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = get_random_string(CONFIG.get('general', 'api_key', fallback=''))
SECRET_KEY = get_random_string(CONFIG.get("general", "api_key", fallback=""))
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = os.getenv('LIBRETIME_DEBUG', False)
DEBUG = os.getenv("LIBRETIME_DEBUG", False)
ALLOWED_HOSTS = ['*']
ALLOWED_HOSTS = ["*"]
# Application definition
INSTALLED_APPS = [
'libretimeapi.apps.LibreTimeAPIConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'url_filter',
"libretimeapi.apps.LibreTimeAPIConfig",
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"rest_framework",
"url_filter",
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
ROOT_URLCONF = 'libretimeapi.urls'
ROOT_URLCONF = "libretimeapi.urls"
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
WSGI_APPLICATION = 'libretimeapi.wsgi.application'
WSGI_APPLICATION = "libretimeapi.wsgi.application"
# Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': CONFIG.get('database', 'dbname', fallback=''),
'USER': CONFIG.get('database', 'dbuser', fallback=''),
'PASSWORD': CONFIG.get('database', 'dbpass', fallback=''),
'HOST': CONFIG.get('database', 'host', fallback=''),
'PORT': '5432',
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": CONFIG.get("database", "dbname", fallback=""),
"USER": CONFIG.get("database", "dbuser", fallback=""),
"PASSWORD": CONFIG.get("database", "dbpass", fallback=""),
"HOST": CONFIG.get("database", "host", fallback=""),
"PORT": "5432",
}
}
@ -90,40 +92,40 @@ DATABASES = {
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework.authentication.SessionAuthentication",
"rest_framework.authentication.BasicAuthentication",
),
'DEFAULT_PERMISSION_CLASSES': [
'libretimeapi.permissions.IsSystemTokenOrUser',
"DEFAULT_PERMISSION_CLASSES": [
"libretimeapi.permissions.IsSystemTokenOrUser",
],
'DEFAULT_FILTER_BACKENDS': [
'url_filter.integrations.drf.DjangoFilterBackend',
"DEFAULT_FILTER_BACKENDS": [
"url_filter.integrations.drf.DjangoFilterBackend",
],
'URL_FIELD_NAME': 'item_url',
"URL_FIELD_NAME": "item_url",
}
# Internationalization
# https://docs.djangoproject.com/en/3.0/topics/i18n/
LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = "en-us"
TIME_ZONE = 'UTC'
TIME_ZONE = "UTC"
USE_I18N = True
@ -135,50 +137,53 @@ USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.0/howto/static-files/
STATIC_URL = '/api/static/'
STATIC_URL = "/api/static/"
if not DEBUG:
STATIC_ROOT = os.getenv('LIBRETIME_STATIC_ROOT', '/usr/share/airtime/api')
STATIC_ROOT = os.getenv("LIBRETIME_STATIC_ROOT", "/usr/share/airtime/api")
AUTH_USER_MODEL = 'libretimeapi.User'
AUTH_USER_MODEL = "libretimeapi.User"
TEST_RUNNER = 'libretimeapi.tests.runners.ManagedModelTestRunner'
TEST_RUNNER = "libretimeapi.tests.runners.ManagedModelTestRunner"
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'simple': {
'format': '{levelname} {message}',
'style': '{',
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"simple": {
"format": "{levelname} {message}",
"style": "{",
},
'verbose': {
'format': '{asctime} {module} {levelname} {message}',
'style': '{',
}
},
'handlers': {
'file': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'filename': os.path.join(CONFIG.get('pypo', 'log_base_dir', fallback='.').replace('\'',''), 'api.log'),
'formatter': 'verbose',
},
'console': {
'level': 'INFO',
'class': 'logging.StreamHandler',
'formatter': 'simple',
"verbose": {
"format": "{asctime} {module} {levelname} {message}",
"style": "{",
},
},
'loggers': {
'django': {
'handlers': ['file', 'console'],
'level': 'INFO',
'propogate': True,
"handlers": {
"file": {
"level": "DEBUG",
"class": "logging.FileHandler",
"filename": os.path.join(
CONFIG.get("pypo", "log_base_dir", fallback=".").replace("'", ""),
"api.log",
),
"formatter": "verbose",
},
'libretimeapi': {
'handlers': ['file', 'console'],
'level': 'INFO',
'propogate': True,
"console": {
"level": "INFO",
"class": "logging.StreamHandler",
"formatter": "simple",
},
},
"loggers": {
"django": {
"handlers": ["file", "console"],
"level": "INFO",
"propagate": True,
},
"libretimeapi": {
"handlers": ["file", "console"],
"level": "INFO",
"propagate": True,
},
},
}

View File

@ -7,18 +7,17 @@ class ManagedModelTestRunner(DiscoverRunner):
project managed for the duration of the test run, so that one doesn't need
to execute the SQL manually to create them.
"""
def setup_test_environment(self, *args, **kwargs):
from django.apps import apps
self.unmanaged_models = [m for m in apps.get_models()
if not m._meta.managed]
self.unmanaged_models = [m for m in apps.get_models() if not m._meta.managed]
for m in self.unmanaged_models:
m._meta.managed = True
super(ManagedModelTestRunner, self).setup_test_environment(*args,
**kwargs)
super(ManagedModelTestRunner, self).setup_test_environment(*args, **kwargs)
def teardown_test_environment(self, *args, **kwargs):
super(ManagedModelTestRunner, self).teardown_test_environment(*args,
**kwargs)
super(ManagedModelTestRunner, self).teardown_test_environment(*args, **kwargs)
# reset unmanaged models
for m in self.unmanaged_models:
m._meta.managed = False

View File

@ -1,40 +1,47 @@
from rest_framework.test import APITestCase
from django.contrib.auth.models import Group
from django.apps import apps
from django.contrib.auth.models import Group
from libretimeapi.models import User
from libretimeapi.models.user_constants import GUEST, DJ
from libretimeapi.models.user_constants import DJ, GUEST
from libretimeapi.permission_constants import GROUPS
from rest_framework.test import APITestCase
class TestUserManager(APITestCase):
def test_create_user(self):
user = User.objects.create_user('test',
email='test@example.com',
password='test',
type=DJ,
first_name='test',
last_name='user')
user = User.objects.create_user(
"test",
email="test@example.com",
password="test",
type=DJ,
first_name="test",
last_name="user",
)
db_user = User.objects.get(pk=user.pk)
self.assertEqual(db_user.username, user.username)
def test_create_superuser(self):
user = User.objects.create_superuser('test',
email='test@example.com',
password='test',
first_name='test',
last_name='user')
user = User.objects.create_superuser(
"test",
email="test@example.com",
password="test",
first_name="test",
last_name="user",
)
db_user = User.objects.get(pk=user.pk)
self.assertEqual(db_user.username, user.username)
class TestUser(APITestCase):
def test_guest_get_group_perms(self):
user = User.objects.create_user('test',
email='test@example.com',
password='test',
type=GUEST,
first_name='test',
last_name='user')
user = User.objects.create_user(
"test",
email="test@example.com",
password="test",
type=GUEST,
first_name="test",
last_name="user",
)
permissions = user.get_group_permissions()
# APIRoot permission hardcoded in the check as it isn't a Permission object
str_perms = [p.codename for p in permissions] + ['view_apiroot']
str_perms = [p.codename for p in permissions] + ["view_apiroot"]
self.assertCountEqual(str_perms, GROUPS[GUEST])

View File

@ -1,12 +1,17 @@
import os
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser
from django.conf import settings
from rest_framework.test import APITestCase, APIRequestFactory
from model_bakery import baker
from libretimeapi.models.user_constants import ADMIN, DJ, GUEST, PROGRAM_MANAGER
from libretimeapi.permission_constants import (
DJ_PERMISSIONS,
GUEST_PERMISSIONS,
PROGRAM_MANAGER_PERMISSIONS,
)
from libretimeapi.permissions import IsSystemTokenOrUser
from libretimeapi.permission_constants import GUEST_PERMISSIONS, DJ_PERMISSIONS, PROGRAM_MANAGER_PERMISSIONS
from libretimeapi.models.user_constants import GUEST, DJ, PROGRAM_MANAGER, ADMIN
from model_bakery import baker
from rest_framework.test import APIRequestFactory, APITestCase
class TestIsSystemTokenOrUser(APITestCase):
@ -15,54 +20,56 @@ class TestIsSystemTokenOrUser(APITestCase):
cls.path = "/api/v2/files/"
def test_unauthorized(self):
response = self.client.get(self.path.format('files'))
response = self.client.get(self.path.format("files"))
self.assertEqual(response.status_code, 403)
def test_token_incorrect(self):
token = 'doesnotexist'
token = "doesnotexist"
request = APIRequestFactory().get(self.path)
request.user = AnonymousUser()
request.META['Authorization'] = 'Api-Key {token}'.format(token=token)
request.META["Authorization"] = "Api-Key {token}".format(token=token)
allowed = IsSystemTokenOrUser().has_permission(request, None)
self.assertFalse(allowed)
def test_token_correct(self):
token = settings.CONFIG.get('general', 'api_key')
token = settings.CONFIG.get("general", "api_key")
request = APIRequestFactory().get(self.path)
request.user = AnonymousUser()
request.META['Authorization'] = 'Api-Key {token}'.format(token=token)
request.META["Authorization"] = "Api-Key {token}".format(token=token)
allowed = IsSystemTokenOrUser().has_permission(request, None)
self.assertTrue(allowed)
class TestPermissions(APITestCase):
URLS = [
'schedule',
'shows',
'show-days',
'show-hosts',
'show-instances',
'show-rebroadcasts',
'files',
'playlists',
'playlist-contents',
'smart-blocks',
'smart-block-contents',
'smart-block-criteria',
'webstreams',
"schedule",
"shows",
"show-days",
"show-hosts",
"show-instances",
"show-rebroadcasts",
"files",
"playlists",
"playlist-contents",
"smart-blocks",
"smart-block-contents",
"smart-block-criteria",
"webstreams",
]
def logged_in_test_model(self, model, name, user_type, fn):
path = self.path.format(model)
user_created = get_user_model().objects.filter(username=name)
if not user_created:
user = get_user_model().objects.create_user(name,
email='test@example.com',
password='test',
type=user_type,
first_name='test',
last_name='user')
self.client.login(username=name, password='test')
user = get_user_model().objects.create_user(
name,
email="test@example.com",
password="test",
type=user_type,
first_name="test",
last_name="user",
)
self.client.login(username=name, password="test")
return fn(path)
@classmethod
@ -71,49 +78,57 @@ class TestPermissions(APITestCase):
def test_guest_permissions_success(self):
for model in self.URLS:
response = self.logged_in_test_model(model, 'guest', GUEST, self.client.get)
self.assertEqual(response.status_code, 200,
msg='Invalid for model {}'.format(model))
response = self.logged_in_test_model(model, "guest", GUEST, self.client.get)
self.assertEqual(
response.status_code, 200, msg="Invalid for model {}".format(model)
)
def test_guest_permissions_failure(self):
for model in self.URLS:
response = self.logged_in_test_model(model, 'guest', GUEST, self.client.post)
self.assertEqual(response.status_code, 403,
msg='Invalid for model {}'.format(model))
response = self.logged_in_test_model('users', 'guest', GUEST, self.client.get)
self.assertEqual(response.status_code, 403, msg='Invalid for model users')
response = self.logged_in_test_model(
model, "guest", GUEST, self.client.post
)
self.assertEqual(
response.status_code, 403, msg="Invalid for model {}".format(model)
)
response = self.logged_in_test_model("users", "guest", GUEST, self.client.get)
self.assertEqual(response.status_code, 403, msg="Invalid for model users")
def test_dj_get_permissions(self):
for model in self.URLS:
response = self.logged_in_test_model(model, 'dj', DJ, self.client.get)
self.assertEqual(response.status_code, 200,
msg='Invalid for model {}'.format(model))
response = self.logged_in_test_model(model, "dj", DJ, self.client.get)
self.assertEqual(
response.status_code, 200, msg="Invalid for model {}".format(model)
)
def test_dj_post_permissions(self):
user = get_user_model().objects.create_user('test-dj',
email='test@example.com',
password='test',
type=DJ,
first_name='test',
last_name='user')
f = baker.make('libretimeapi.File',
owner=user)
model = 'files/{}'.format(f.id)
user = get_user_model().objects.create_user(
"test-dj",
email="test@example.com",
password="test",
type=DJ,
first_name="test",
last_name="user",
)
f = baker.make("libretimeapi.File", owner=user)
model = "files/{}".format(f.id)
path = self.path.format(model)
self.client.login(username='test-dj', password='test')
response = self.client.patch(path, {'name': 'newFilename'})
self.client.login(username="test-dj", password="test")
response = self.client.patch(path, {"name": "newFilename"})
self.assertEqual(response.status_code, 200)
def test_dj_post_permissions_failure(self):
user = get_user_model().objects.create_user('test-dj',
email='test@example.com',
password='test',
type=DJ,
first_name='test',
last_name='user')
f = baker.make('libretimeapi.File')
model = 'files/{}'.format(f.id)
user = get_user_model().objects.create_user(
"test-dj",
email="test@example.com",
password="test",
type=DJ,
first_name="test",
last_name="user",
)
f = baker.make("libretimeapi.File")
model = "files/{}".format(f.id)
path = self.path.format(model)
self.client.login(username='test-dj', password='test')
response = self.client.patch(path, {'name': 'newFilename'})
self.client.login(username="test-dj", password="test")
response = self.client.patch(path, {"name": "newFilename"})
self.assertEqual(response.status_code, 403)

View File

@ -1,38 +1,42 @@
import os
from django.contrib.auth.models import AnonymousUser
from django.conf import settings
from rest_framework.test import APITestCase, APIRequestFactory
from model_bakery import baker
from django.contrib.auth.models import AnonymousUser
from libretimeapi.views import FileViewSet
from model_bakery import baker
from rest_framework.test import APIRequestFactory, APITestCase
class TestFileViewSet(APITestCase):
@classmethod
def setUpTestData(cls):
cls.path = "/api/v2/files/{id}/download/"
cls.token = settings.CONFIG.get('general', 'api_key')
cls.token = settings.CONFIG.get("general", "api_key")
def test_invalid(self):
path = self.path.format(id='a')
self.client.credentials(HTTP_AUTHORIZATION='Api-Key {}'.format(self.token))
path = self.path.format(id="a")
self.client.credentials(HTTP_AUTHORIZATION="Api-Key {}".format(self.token))
response = self.client.get(path)
self.assertEqual(response.status_code, 400)
def test_does_not_exist(self):
path = self.path.format(id='1')
self.client.credentials(HTTP_AUTHORIZATION='Api-Key {}'.format(self.token))
path = self.path.format(id="1")
self.client.credentials(HTTP_AUTHORIZATION="Api-Key {}".format(self.token))
response = self.client.get(path)
self.assertEqual(response.status_code, 404)
def test_exists(self):
music_dir = baker.make('libretimeapi.MusicDir',
directory=os.path.join(os.path.dirname(__file__),
'resources'))
f = baker.make('libretimeapi.File',
directory=music_dir,
mime='audio/mp3',
filepath='song.mp3')
music_dir = baker.make(
"libretimeapi.MusicDir",
directory=os.path.join(os.path.dirname(__file__), "resources"),
)
f = baker.make(
"libretimeapi.File",
directory=music_dir,
mime="audio/mp3",
filepath="song.mp3",
)
path = self.path.format(id=str(f.pk))
self.client.credentials(HTTP_AUTHORIZATION='Api-Key {}'.format(self.token))
self.client.credentials(HTTP_AUTHORIZATION="Api-Key {}".format(self.token))
response = self.client.get(path)
self.assertEqual(response.status_code, 200)

View File

@ -4,48 +4,48 @@ from rest_framework import routers
from .views import *
router = routers.DefaultRouter()
router.register('smart-blocks', SmartBlockViewSet)
router.register('smart-block-contents', SmartBlockContentViewSet)
router.register('smart-block-criteria', SmartBlockCriteriaViewSet)
router.register('countries', CountryViewSet)
router.register('files', FileViewSet)
router.register('listener-counts', ListenerCountViewSet)
router.register('live-logs', LiveLogViewSet)
router.register('login-attempts', LoginAttemptViewSet)
router.register('mount-names', MountNameViewSet)
router.register('music-dirs', MusicDirViewSet)
router.register('playlists', PlaylistViewSet)
router.register('playlist-contents', PlaylistContentViewSet)
router.register('playout-history', PlayoutHistoryViewSet)
router.register('playout-history-metadata', PlayoutHistoryMetadataViewSet)
router.register('playout-history-templates', PlayoutHistoryTemplateViewSet)
router.register('playout-history-template-fields', PlayoutHistoryTemplateFieldViewSet)
router.register('preferences', PreferenceViewSet)
router.register('schedule', ScheduleViewSet)
router.register('service-registers', ServiceRegisterViewSet)
router.register('sessions', SessionViewSet)
router.register('shows', ShowViewSet)
router.register('show-days', ShowDaysViewSet)
router.register('show-hosts', ShowHostViewSet)
router.register('show-instances', ShowInstanceViewSet)
router.register('show-rebroadcasts', ShowRebroadcastViewSet)
router.register('stream-settings', StreamSettingViewSet)
router.register('users', UserViewSet)
router.register('user-tokens', UserTokenViewSet)
router.register('timestamps', TimestampViewSet)
router.register('webstreams', WebstreamViewSet)
router.register('webstream-metadata', WebstreamMetadataViewSet)
router.register('celery-tasks', CeleryTaskViewSet)
router.register('cloud-files', CloudFileViewSet)
router.register('imported-podcasts', ImportedPodcastViewSet)
router.register('podcasts', PodcastViewSet)
router.register('podcast-episodes', PodcastEpisodeViewSet)
router.register('station-podcasts', StationPodcastViewSet)
router.register('third-party-track-references', ThirdPartyTrackReferenceViewSet)
router.register('track-types', TrackTypeViewSet)
router.register("smart-blocks", SmartBlockViewSet)
router.register("smart-block-contents", SmartBlockContentViewSet)
router.register("smart-block-criteria", SmartBlockCriteriaViewSet)
router.register("countries", CountryViewSet)
router.register("files", FileViewSet)
router.register("listener-counts", ListenerCountViewSet)
router.register("live-logs", LiveLogViewSet)
router.register("login-attempts", LoginAttemptViewSet)
router.register("mount-names", MountNameViewSet)
router.register("music-dirs", MusicDirViewSet)
router.register("playlists", PlaylistViewSet)
router.register("playlist-contents", PlaylistContentViewSet)
router.register("playout-history", PlayoutHistoryViewSet)
router.register("playout-history-metadata", PlayoutHistoryMetadataViewSet)
router.register("playout-history-templates", PlayoutHistoryTemplateViewSet)
router.register("playout-history-template-fields", PlayoutHistoryTemplateFieldViewSet)
router.register("preferences", PreferenceViewSet)
router.register("schedule", ScheduleViewSet)
router.register("service-registers", ServiceRegisterViewSet)
router.register("sessions", SessionViewSet)
router.register("shows", ShowViewSet)
router.register("show-days", ShowDaysViewSet)
router.register("show-hosts", ShowHostViewSet)
router.register("show-instances", ShowInstanceViewSet)
router.register("show-rebroadcasts", ShowRebroadcastViewSet)
router.register("stream-settings", StreamSettingViewSet)
router.register("users", UserViewSet)
router.register("user-tokens", UserTokenViewSet)
router.register("timestamps", TimestampViewSet)
router.register("webstreams", WebstreamViewSet)
router.register("webstream-metadata", WebstreamMetadataViewSet)
router.register("celery-tasks", CeleryTaskViewSet)
router.register("cloud-files", CloudFileViewSet)
router.register("imported-podcasts", ImportedPodcastViewSet)
router.register("podcasts", PodcastViewSet)
router.register("podcast-episodes", PodcastEpisodeViewSet)
router.register("station-podcasts", StationPodcastViewSet)
router.register("third-party-track-references", ThirdPartyTrackReferenceViewSet)
router.register("track-types", TrackTypeViewSet)
urlpatterns = [
path('api/v2/', include(router.urls)),
path('api/v2/version/', version),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
path("api/v2/", include(router.urls)),
path("api/v2/version/", version),
path("api-auth/", include("rest_framework.urls", namespace="rest_framework")),
]

View File

@ -1,7 +1,8 @@
import configparser
import sys
import string
import random
import string
import sys
def read_config_file(config_path):
"""Parse the application's config file located at config_path."""
@ -9,17 +10,20 @@ def read_config_file(config_path):
try:
config.readfp(open(config_path))
except IOError as e:
print("Failed to open config file at {}: {}".format(config_path, e.strerror),
file=sys.stderr)
print(
"Failed to open config file at {}: {}".format(config_path, e.strerror),
file=sys.stderr,
)
raise e
except Exception as e:
print(e.strerror, file=sys.stderr)
raise e
return config
def get_random_string(seed):
"""Generates a random string based on the given seed"""
choices = string.ascii_letters + string.digits + string.punctuation
seed = seed.encode('utf-8')
seed = seed.encode("utf-8")
rand = random.Random(seed)
return [rand.choice(choices) for i in range(16)]

View File

@ -1,228 +1,271 @@
import os
from django.conf import settings
from django.http import FileResponse
from django.shortcuts import get_object_or_404
from rest_framework import status, viewsets
from rest_framework.decorators import api_view, action, permission_classes
from rest_framework.decorators import action, api_view, permission_classes
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from .serializers import *
from .permissions import IsAdminOrOwnUser
from .serializers import *
class UserViewSet(viewsets.ModelViewSet):
queryset = get_user_model().objects.all()
serializer_class = UserSerializer
permission_classes = [IsAdminOrOwnUser]
model_permission_name = 'user'
model_permission_name = "user"
class SmartBlockViewSet(viewsets.ModelViewSet):
queryset = SmartBlock.objects.all()
serializer_class = SmartBlockSerializer
model_permission_name = 'smartblock'
model_permission_name = "smartblock"
class SmartBlockContentViewSet(viewsets.ModelViewSet):
queryset = SmartBlockContent.objects.all()
serializer_class = SmartBlockContentSerializer
model_permission_name = 'smartblockcontent'
model_permission_name = "smartblockcontent"
class SmartBlockCriteriaViewSet(viewsets.ModelViewSet):
queryset = SmartBlockCriteria.objects.all()
serializer_class = SmartBlockCriteriaSerializer
model_permission_name = 'smartblockcriteria'
model_permission_name = "smartblockcriteria"
class CountryViewSet(viewsets.ModelViewSet):
queryset = Country.objects.all()
serializer_class = CountrySerializer
model_permission_name = 'country'
model_permission_name = "country"
class FileViewSet(viewsets.ModelViewSet):
queryset = File.objects.all()
serializer_class = FileSerializer
model_permission_name = 'file'
model_permission_name = "file"
@action(detail=True, methods=['GET'])
@action(detail=True, methods=["GET"])
def download(self, request, pk=None):
if pk is None:
return Response('No file requested', status=status.HTTP_400_BAD_REQUEST)
return Response("No file requested", status=status.HTTP_400_BAD_REQUEST)
try:
pk = int(pk)
except ValueError:
return Response('File ID should be an integer',
status=status.HTTP_400_BAD_REQUEST)
return Response(
"File ID should be an integer", status=status.HTTP_400_BAD_REQUEST
)
filename = get_object_or_404(File, pk=pk)
directory = filename.directory
path = os.path.join(directory.directory, filename.filepath)
response = FileResponse(open(path, 'rb'), content_type=filename.mime)
response = FileResponse(open(path, "rb"), content_type=filename.mime)
return response
class ListenerCountViewSet(viewsets.ModelViewSet):
queryset = ListenerCount.objects.all()
serializer_class = ListenerCountSerializer
model_permission_name = 'listenercount'
model_permission_name = "listenercount"
class LiveLogViewSet(viewsets.ModelViewSet):
queryset = LiveLog.objects.all()
serializer_class = LiveLogSerializer
model_permission_name = 'livelog'
model_permission_name = "livelog"
class LoginAttemptViewSet(viewsets.ModelViewSet):
queryset = LoginAttempt.objects.all()
serializer_class = LoginAttemptSerializer
model_permission_name = 'loginattempt'
model_permission_name = "loginattempt"
class MountNameViewSet(viewsets.ModelViewSet):
queryset = MountName.objects.all()
serializer_class = MountNameSerializer
model_permission_name = 'mountname'
model_permission_name = "mountname"
class MusicDirViewSet(viewsets.ModelViewSet):
queryset = MusicDir.objects.all()
serializer_class = MusicDirSerializer
model_permission_name = 'musicdir'
model_permission_name = "musicdir"
class PlaylistViewSet(viewsets.ModelViewSet):
queryset = Playlist.objects.all()
serializer_class = PlaylistSerializer
model_permission_name = 'playlist'
model_permission_name = "playlist"
class PlaylistContentViewSet(viewsets.ModelViewSet):
queryset = PlaylistContent.objects.all()
serializer_class = PlaylistContentSerializer
model_permission_name = 'playlistcontent'
model_permission_name = "playlistcontent"
class PlayoutHistoryViewSet(viewsets.ModelViewSet):
queryset = PlayoutHistory.objects.all()
serializer_class = PlayoutHistorySerializer
model_permission_name = 'playouthistory'
model_permission_name = "playouthistory"
class PlayoutHistoryMetadataViewSet(viewsets.ModelViewSet):
queryset = PlayoutHistoryMetadata.objects.all()
serializer_class = PlayoutHistoryMetadataSerializer
model_permission_name = 'playouthistorymetadata'
model_permission_name = "playouthistorymetadata"
class PlayoutHistoryTemplateViewSet(viewsets.ModelViewSet):
queryset = PlayoutHistoryTemplate.objects.all()
serializer_class = PlayoutHistoryTemplateSerializer
model_permission_name = 'playouthistorytemplate'
model_permission_name = "playouthistorytemplate"
class PlayoutHistoryTemplateFieldViewSet(viewsets.ModelViewSet):
queryset = PlayoutHistoryTemplateField.objects.all()
serializer_class = PlayoutHistoryTemplateFieldSerializer
model_permission_name = 'playouthistorytemplatefield'
model_permission_name = "playouthistorytemplatefield"
class PreferenceViewSet(viewsets.ModelViewSet):
queryset = Preference.objects.all()
serializer_class = PreferenceSerializer
model_permission_name = 'perference'
model_permission_name = "preference"
class ScheduleViewSet(viewsets.ModelViewSet):
queryset = Schedule.objects.all()
serializer_class = ScheduleSerializer
filter_fields = ('starts', 'ends', 'playout_status', 'broadcasted')
model_permission_name = 'schedule'
filter_fields = ("starts", "ends", "playout_status", "broadcasted")
model_permission_name = "schedule"
class ServiceRegisterViewSet(viewsets.ModelViewSet):
queryset = ServiceRegister.objects.all()
serializer_class = ServiceRegisterSerializer
model_permission_name = 'serviceregister'
model_permission_name = "serviceregister"
class SessionViewSet(viewsets.ModelViewSet):
queryset = Session.objects.all()
serializer_class = SessionSerializer
model_permission_name = 'session'
model_permission_name = "session"
class ShowViewSet(viewsets.ModelViewSet):
queryset = Show.objects.all()
serializer_class = ShowSerializer
model_permission_name = 'show'
model_permission_name = "show"
class ShowDaysViewSet(viewsets.ModelViewSet):
queryset = ShowDays.objects.all()
serializer_class = ShowDaysSerializer
model_permission_name = 'showdays'
model_permission_name = "showdays"
class ShowHostViewSet(viewsets.ModelViewSet):
queryset = ShowHost.objects.all()
serializer_class = ShowHostSerializer
model_permission_name = 'showhost'
model_permission_name = "showhost"
class ShowInstanceViewSet(viewsets.ModelViewSet):
queryset = ShowInstance.objects.all()
serializer_class = ShowInstanceSerializer
model_permission_name = 'showinstance'
model_permission_name = "showinstance"
class ShowRebroadcastViewSet(viewsets.ModelViewSet):
queryset = ShowRebroadcast.objects.all()
serializer_class = ShowRebroadcastSerializer
model_permission_name = 'showrebroadcast'
model_permission_name = "showrebroadcast"
class StreamSettingViewSet(viewsets.ModelViewSet):
queryset = StreamSetting.objects.all()
serializer_class = StreamSettingSerializer
model_permission_name = 'streamsetting'
model_permission_name = "streamsetting"
class UserTokenViewSet(viewsets.ModelViewSet):
queryset = UserToken.objects.all()
serializer_class = UserTokenSerializer
model_permission_name = 'usertoken'
model_permission_name = "usertoken"
class TimestampViewSet(viewsets.ModelViewSet):
queryset = Timestamp.objects.all()
serializer_class = TimestampSerializer
model_permission_name = 'timestamp'
model_permission_name = "timestamp"
class WebstreamViewSet(viewsets.ModelViewSet):
queryset = Webstream.objects.all()
serializer_class = WebstreamSerializer
model_permission_name = 'webstream'
model_permission_name = "webstream"
class WebstreamMetadataViewSet(viewsets.ModelViewSet):
queryset = WebstreamMetadata.objects.all()
serializer_class = WebstreamMetadataSerializer
model_permission_name = 'webstreametadata'
model_permission_name = "webstreametadata"
class CeleryTaskViewSet(viewsets.ModelViewSet):
queryset = CeleryTask.objects.all()
serializer_class = CeleryTaskSerializer
model_permission_name = 'celerytask'
model_permission_name = "celerytask"
class CloudFileViewSet(viewsets.ModelViewSet):
queryset = CloudFile.objects.all()
serializer_class = CloudFileSerializer
model_permission_name = 'cloudfile'
model_permission_name = "cloudfile"
class ImportedPodcastViewSet(viewsets.ModelViewSet):
queryset = ImportedPodcast.objects.all()
serializer_class = ImportedPodcastSerializer
model_permission_name = 'importedpodcast'
model_permission_name = "importedpodcast"
class PodcastViewSet(viewsets.ModelViewSet):
queryset = Podcast.objects.all()
serializer_class = PodcastSerializer
model_permission_name = 'podcast'
model_permission_name = "podcast"
class PodcastEpisodeViewSet(viewsets.ModelViewSet):
queryset = PodcastEpisode.objects.all()
serializer_class = PodcastEpisodeSerializer
model_permission_name = 'podcastepisode'
model_permission_name = "podcastepisode"
class StationPodcastViewSet(viewsets.ModelViewSet):
queryset = StationPodcast.objects.all()
serializer_class = StationPodcastSerializer
model_permission_name = 'station'
model_permission_name = "station"
class ThirdPartyTrackReferenceViewSet(viewsets.ModelViewSet):
queryset = ThirdPartyTrackReference.objects.all()
serializer_class = ThirdPartyTrackReferenceSerializer
model_permission_name = 'thirdpartytrackreference'
model_permission_name = "thirdpartytrackreference"
class TrackTypeViewSet(viewsets.ModelViewSet):
queryset = TrackType.objects.all()
serializer_class = TrackTypeSerializer
model_permission_name = 'tracktype'
model_permission_name = "tracktype"
@api_view(['GET'])
@permission_classes((AllowAny, ))
@api_view(["GET"])
@permission_classes((AllowAny,))
def version(request, *args, **kwargs):
return Response({'api_version': settings.API_VERSION})
return Response({"api_version": settings.API_VERSION})

View File

@ -11,6 +11,6 @@ import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'libretimeapi.settings')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "libretimeapi.settings")
application = get_wsgi_application()

View File

@ -1,32 +1,33 @@
import os
import shutil
from setuptools import setup, find_packages
from setuptools import find_packages, setup
script_path = os.path.dirname(os.path.realpath(__file__))
print(script_path)
os.chdir(script_path)
setup(
name='libretime-api',
version='2.0.0a1',
name="libretime-api",
version="2.0.0a1",
packages=find_packages(),
include_package_data=True,
description='LibreTime API backend server',
url='https://github.com/LibreTime/libretime',
author='LibreTime Contributors',
scripts=['bin/libretime-api'],
description="LibreTime API backend server",
url="https://github.com/LibreTime/libretime",
author="LibreTime Contributors",
scripts=["bin/libretime-api"],
install_requires=[
'coreapi',
'Django~=3.0',
'djangorestframework',
'django-url-filter',
'markdown',
'model_bakery',
'psycopg2',
"coreapi",
"Django~=3.0",
"djangorestframework",
"django-url-filter",
"markdown",
"model_bakery",
"psycopg2",
],
project_urls={
'Bug Tracker': 'https://github.com/LibreTime/libretime/issues',
'Documentation': 'https://libretime.org',
'Source Code': 'https://github.com/LibreTime/libretime',
"Bug Tracker": "https://github.com/LibreTime/libretime/issues",
"Documentation": "https://libretime.org",
"Source Code": "https://github.com/LibreTime/libretime",
},
)

View File

@ -25,4 +25,3 @@ else
echo "could not detect version for VERSION file" > VERSION
fi
fi

View File

@ -49,14 +49,14 @@
* Much faster library import (Silan analyzer runs in background)
* Fixed zombie process sometimes being created
* Other
* Upgrade to Mutagen (tag reader) 1.21
* Upgrade to Mutagen (tag reader) 1.21
2.3.0 - Jan 21st, 2013
* New features
* Localization (Chinese, Czech, English, French, German, Italian, Korean,
Portuguese, Russian, Spanish)
* User management page for non-admin users
* Listener statistics (Icecast/Shoutcast)
* Listener statistics (Icecast/Shoutcast)
* Airtime no longer requires Apache document root
* Replay Gain offset in real-time
* Enable/disable replay gain
@ -110,10 +110,10 @@
* fixed master/source override URL being reverted to original setting after clicking 'Save' in stream settings.
* Add several helpful tips in the Stream Settings page and some UI cleanup
* DJ user type cannot delete Playlists that aren't their own or delete tracks
* Playlist Builder should remember your position instead of reseting to the first page everytime an operation was performed
* Playlist Builder should remember your position instead of resetting to the first page every time an operation was performed
* If Master or Live input source is disconnected, Airtime will no longer automatically switch off that source. This should allow the source to
reconnect and continue playback.
* Bug fixes
* Fixed playout engine sometimes not receiving new schedule which could result in dead air
* Fixed script timeout which caused Apache to become unresponsive
@ -157,7 +157,7 @@
* "Listen" preview player no longer falls behind the broadcast (you can only mute the stream now, not stop it)
* Tracks that cannot be played will be rejected on upload and put in to the directory "/srv/airtime/stor/problem_files" (but currently it will not tell you that it rejected them - sorry\!)
* Library is automatically refreshed when media import is finished
* Show "Disk Full" message when trying to upload a file that wont fit on the disk
* Show "Disk Full" message when trying to upload a file that won't fit on the disk
* Reduced CPU utilization for OGG streams
* New command line utilities:
* airtime-test-soundcard - verify that the soundcard is working
@ -174,7 +174,7 @@
* Fixed Airtime could stop automatically recording after 2 hours if the web interface isn't used.
* Fixed upgrading from 1.8.2 when the stor directory was a symlink would cause filenames to not be preserved.
* Fixed Day View in the Now Playing tab showed some items on incorrect days.
* Fixed problems with having an equal '=' sign as an icecast password
* Fixed problems with having an equal '=' sign as an icecast password
* Other
* Various optimizations to make Airtime feel snappier in the browser. Various views should
load much quicker.
@ -237,7 +237,7 @@
* Better error checking in cases where two users alter the same data at
the same time (for example, in playlists and shows)
* Playlists: Removed intermediate "Add Playlist" screen where it asked you
to fill in the name and description of the playlist. This wasnt
to fill in the name and description of the playlist. This wasn't
necessary since everything could be changed from the playlist editor
itself.
* Added "airtime-log" command to display, dump, and view all of Airtime's
@ -295,11 +295,11 @@
- Fixed pypo hanging if web server is unavailable
- Fixed items that were being dragged and dropped in the Playlist Builder
being obscured by other UI elements.
1.9.3 - August 26th, 2011
* Improvements
- It is now possible to upgrade your system while a show is playing.
Playout will be temporarily interrupted for about 5-10 seconds and then
Playout will be temporarily interrupted for about 5-10 seconds and then
playout will resume. Previously playout would not resume until the next
scheduled show.
* Fixes
@ -324,7 +324,7 @@
- Prevent users from doing a manual install of Airtime if they already have
the Debian package version installed
* Changes
- Support Settings moved to a seperate page accessible by Admin user only.
- Support Settings moved to a separate page accessible by Admin user only.
1.9.0 - August 9, 2011
@ -333,20 +333,20 @@ The cool stuff:
- Human-readable file structure. The directory structure and file names on
disk are now human-readable. This means you can easily find files using
your file browser on your server.
- Magic file synchronization. Edits to your files are automatically
- Magic file synchronization. Edits to your files are automatically
noticed by Airtime. If you edit any files on disk, such as trimming the
length of a track, Airtime will automatically notice this and adjust the
playlist lengths and shows for that audio file.
- Auto-import and multiple-directory support. You can set any number of
directories to be watched by Airtime. Any new files you add to watched
- Auto-import and multiple-directory support. You can set any number of
directories to be watched by Airtime. Any new files you add to watched
directories will be automatically imported into Airtime, and any deleted
files will be automatically removed.
- The "airtime-import" command line tool can now set watched directories
- The "airtime-import" command line tool can now set watched directories
and change the storage directory.
- Graceful recovery from reboot. If the playout engine starts up and
detects that a show should be playing at the current time, it will skip
to the right point in the track and start playing. Previously, Airtime
would not play anything until the next show started. This also fixes a
to the right point in the track and start playing. Previously, Airtime
would not play anything until the next show started. This also fixes a
problem where the metadata on the stream was lost when a file had
cue-in/out values set. Thanks to the Liquidsoap developers for
implementing the ability to do all of this!
@ -354,28 +354,28 @@ The cool stuff:
- A new "Program Manager" role. A program manager can create shows but
can't change the preferences or modify users.
- No more rebooting after install! Airtime now uses standard SystemV initd
scripts instead of non-standard daemontools. This also makes for a much
scripts instead of non-standard daemontools. This also makes for a much
faster install.
- Frontend widgets are much easier to use and their theme can be modified
- Frontend widgets are much easier to use and their theme can be modified
with CSS (Click here for more info and installation instructions).
- Improved installation - only one command to install on Ubuntu!
* Improvements:
- Cumulative time shown on playlists. The Playlist Builder now shows the
- Cumulative time shown on playlists. The Playlist Builder now shows the
total time since the beginning of the playlist for each song.
- "End Time" instead of "Duration". In the Add/Edit Show dialog, we
- "End Time" instead of "Duration". In the Add/Edit Show dialog, we
replaced the "Duration" field with "End Time". Users reported that this
was a much more intuitive way to schedule the show. Duration is still
shown as a read-only field.
- Feedback & promotion system. Airtime now includes a way to send feedback
and promote your site on the Sourcefabric web page. This will greatly
enhance our ability to understand who is using the software, which in
turn will allow us to make appropriate features and receive grant
and promote your site on the Sourcefabric web page. This will greatly
enhance our ability to understand who is using the software, which in
turn will allow us to make appropriate features and receive grant
funding.
- The show recorder can now instantly cancel a show thanks to the use of
RabbitMQ.
- Only admins have the ability to delete files now.
- The playout engine now runs with a higher priority. This should help
- The playout engine now runs with a higher priority. This should help
prevent any problems with audio skipping.
- Airtime has been contained. It is now easier to run other apps on the
same system with Airtime because it no longer messes with the system-wide
@ -386,12 +386,12 @@ The cool stuff:
page( above the search box).
* Bug fixes:
- Fixed bug where you couldn't import a file with a name longer than 255
- Fixed bug where you couldn't import a file with a name longer than 255
characters.
- Fixed bug where searching an audio archive of 15K+ files was slow.
- Fixed bug where upgrading from more than one version back
(e.g. 1.8.0 -> 1.9.0) did not work.
- Fixed bug where the wrong file length was reported for very large CBR
- Fixed bug where the wrong file length was reported for very large CBR
mp3 files (thanks to mutagen developers for the patch!)
1.8.2 - June 8, 2011
@ -456,30 +456,30 @@ Highlights:
1.8.0 - April 19, 2011
* The biggest feature of this release is the ability to edit shows. You can
change everything from Name, Description, and URL, to repeat and
change everything from 'Name', 'Description', and 'URL', to repeat and
rebroadcast days. Show instances will be dynamically created or removed as
needed. Radio stations will be pleased to know they can now have up to
needed. Radio stations will be pleased to know they can now have up to
ten rebroadcast shows too.
* Airtimes calendar now looks, feels and performs better than ever. Loading
a station schedule is now five to eight times faster. In our tests of 1.7,
if the month calendar had shows scheduled for every hour of every day, it
* Airtime's calendar now looks, feels and performs better than ever. Loading
a station schedule is now five to eight times faster. In our tests of 1.7,
if the month calendar had shows scheduled for every hour of every day, it
used to take 16 seconds to load. Now in 1.8 it takes two seconds.
* It is possible to have up to ten rebroadcast shows now, in 1.7 it was only
up to five.
* Airtimes new installation script has two options for increased install
flexibility: --preserve to keep your existing config files, or --overwrite
to replace your existing config files with new ones. Uninstall no longer
* Airtime's new installation script has two options for increased install
flexibility: --preserve to keep your existing config files, or --overwrite
to replace your existing config files with new ones. Uninstall no longer
removes Airtime config files or the music storage directory.
* New improved look & feel of the calendar (thanks to the "FullCalendar"
* New improved look & feel of the calendar (thanks to the "FullCalendar"
jQuery project).
* Installation now puts files in standard locations in the Linux file
* Installation now puts files in standard locations in the Linux file
hierarchy, which prepares the project to be accepted into Ubuntu and Debian.
Also because of our wish to be part of those projects, the default output
stream type is now OGG instead of MP3 -- due to MP3 licensing issues.
Also because of our wish to be part of those projects, the default output
stream type is now OGG instead of MP3 -- due to MP3 licensing issues.
This configuration can be changed in "/etc/airtime/liquidsoap.conf".
* You now have the ability to start and stop pypo and the show recorder from
the command line with the commands "airtime-pypo-start",
"airtime-pypo-stop", "airtime-show-recorder-start", and
* You now have the ability to start and stop pypo and the show recorder from
the command line with the commands "airtime-pypo-start",
"airtime-pypo-stop", "airtime-show-recorder-start", and
"airtime-show-recorder-stop".
* Bug fixes:
- CC-2192 Schedule sent to pypo is not sorted by start time.
@ -520,7 +520,7 @@ Highlights:
* Bug fixes:
- CC-2082 OGG stream dies after every song when using MPlayer
- CC-1894 Warn users about time zone differences or clock drift problems on
the server
the server
- CC-2058 Utilities are not in the system $PATH
- CC-2051 Unable to change user password
- CC-2030 Icon needed for Cue In/Out
@ -531,7 +531,7 @@ Bug fixes:
* CC-1973 Liquidsoap crashes after multi-day playout
* CC-1970 API key fix (Security fix) - Each time you run the install scripts,
a new API key is now generated.
* CC-1992 Editing metadata goes blank on 'submit'
* CC-1992 Editing metadata goes blank on 'submit'
* CC-1993 ui start time and song time unsynchronized
1.6.0 - Feb 14, 2011

View File

@ -1,10 +1,10 @@
# Maintainer: Zachary Klosko (kloskoz@vcu.edu)
hostname: libretimeTest
timezone: America/New York # change as needed
timezone: America/New York # change as needed
ntp:
pools: ['north-america.pool.ntp.org']
servers: ['0.north-america.pool.ntp.org', '0.pool.ntp.org']
pools: ["north-america.pool.ntp.org"]
servers: ["0.north-america.pool.ntp.org", "0.pool.ntp.org"]
password: hackme
chpasswd: { expire: False }
@ -17,4 +17,4 @@ apt_upgrade: true
# Clone repo on init (Change repo url if needed)
# If you need to clone a branch, use git clone --branch branchname repourl
runcmd:
- git clone https://github.com/LibreTime/libretime.git
- git clone https://github.com/LibreTime/libretime.git

View File

@ -1,45 +1,50 @@
{
"autoload": {
"classmap": ["airtime_mvc/application/"]
},
"autoload-dev": {
"classmap": ["airtime_mvc/tests/application/", "vendor/phpunit/dbunit/src/"]
},
"require": {
"james-heinrich/getid3": "dev-master",
"propel/propel1": "1.7.0-stable",
"aws/aws-sdk-php": "2.7.9",
"raven/raven": "0.12.0",
"massivescale/celery-php": "2.0.*@dev",
"simplepie/simplepie": "dev-master",
"zf1s/zend-application": "^1.12",
"composer/semver": "^1.4",
"php-amqplib/php-amqplib": "^2.6",
"zf1s/zend-acl": "^1.12",
"zf1s/zend-session": "^1.12",
"zf1s/zend-navigation": "^1.12",
"zf1s/zend-controller": "^1.12",
"zf1s/zend-log": "^1.12",
"zf1s/zend-version": "^1.12",
"zf1s/zend-rest": "dev-master@dev",
"zf1s/zend-layout": "^1.12",
"zf1s/zend-loader": "dev-master@dev",
"zf1s/zend-auth": "^1.12",
"zf1s/zend-filter": "^1.12",
"zf1s/zend-json": "^1.12",
"zf1s/zend-form": "^1.12",
"zf1s/zend-db": "^1.12",
"zf1s/zend-file": "^1.12",
"zf1s/zend-file-transfer": "^1.12",
"zf1s/zend-http": "^1.12",
"zf1s/zend-date": "^1.12",
"zf1s/zend-view": "dev-master@dev",
"zf1s/zend-validate": "dev-master@dev",
"zf1s/zend-cache": "dev-master@dev"
},
"require-dev": {
"phpunit/phpunit": "^4.3",
"phpunit/dbunit": "^2.0",
"zf1s/zend-test": "^1.12"
}
"autoload": {
"classmap": [
"airtime_mvc/application/"
]
},
"autoload-dev": {
"classmap": [
"airtime_mvc/tests/application/",
"vendor/phpunit/dbunit/src/"
]
},
"require": {
"james-heinrich/getid3": "dev-master",
"propel/propel1": "1.7.0-stable",
"aws/aws-sdk-php": "2.7.9",
"raven/raven": "0.12.0",
"massivescale/celery-php": "2.0.*@dev",
"simplepie/simplepie": "dev-master",
"zf1s/zend-application": "^1.12",
"composer/semver": "^1.4",
"php-amqplib/php-amqplib": "^2.6",
"zf1s/zend-acl": "^1.12",
"zf1s/zend-session": "^1.12",
"zf1s/zend-navigation": "^1.12",
"zf1s/zend-controller": "^1.12",
"zf1s/zend-log": "^1.12",
"zf1s/zend-version": "^1.12",
"zf1s/zend-rest": "dev-master@dev",
"zf1s/zend-layout": "^1.12",
"zf1s/zend-loader": "dev-master@dev",
"zf1s/zend-auth": "^1.12",
"zf1s/zend-filter": "^1.12",
"zf1s/zend-json": "^1.12",
"zf1s/zend-form": "^1.12",
"zf1s/zend-db": "^1.12",
"zf1s/zend-file": "^1.12",
"zf1s/zend-file-transfer": "^1.12",
"zf1s/zend-http": "^1.12",
"zf1s/zend-date": "^1.12",
"zf1s/zend-view": "dev-master@dev",
"zf1s/zend-validate": "dev-master@dev",
"zf1s/zend-cache": "dev-master@dev"
},
"require-dev": {
"phpunit/phpunit": "^4.3",
"phpunit/dbunit": "^2.0",
"zf1s/zend-test": "^1.12"
}
}

View File

@ -7,5 +7,5 @@ To update the Airtime translations:
- Commit the updated files.
- Push to GitHub.
- Transifex will then pick up the updated files in about 24 hours, and they'll be available for translation there.
- After translators have updated strings, they'll be automatically downloaded and committed to our git repo by
- After translators have updated strings, they'll be automatically downloaded and committed to our git repo by
a script running here at Sourcefabric (contact Andrey).

View File

@ -1,13 +1,13 @@
<?PHP
/*
/*
The purpose of this script is to take a file from cc_files table, and insert it into
the schedule table. DB columns at the time of writing are
starts | ends | file_id | clip_length | fade_in | fade_out | cue_in | cue_out | media_item_played | instance_id
an example of data in this row is:
"9" | "2012-02-29 17:10:00" | "2012-02-29 17:15:05.037166" | 1 | "00:05:05.037166" | "00:00:00" | "00:00:00" | "00:00:00" | "00:05:05.037166" | FALSE | 5
@ -19,25 +19,25 @@ function query($conn, $query){
echo "Error executing query $query.\n";
exit(1);
}
return $result;
}
function getFileFromCcFiles($conn){
$query = "SELECT * from cc_files LIMIT 2";
$result = query($conn, $query);
$files = array();
while ($row = pg_fetch_array($result)) {
$files[] = $row;
}
if (count($files) == 0){
echo "Library is empty. Could not choose random file.";
exit(1);
}
return $files;
}
@ -60,7 +60,7 @@ function insertIntoCcShow($conn){
while ($row = pg_fetch_array($result)) {
$show_id = $row["currval"];
}
return $show_id;
}
@ -70,7 +70,7 @@ function insertIntoCcShowInstances($conn, $show_id, $starts, $ends, $files){
* Column values:
* starts | ends | show_id | record | rebroadcast | instance_id | file_id | time_filled | last_scheduled | modified_instance
* */
$nowDateTime = new DateTime("now", new DateTimeZone("UTC"));
$now = $nowDateTime->format("Y-m-d H:i:s");
@ -79,7 +79,7 @@ function insertIntoCcShowInstances($conn, $show_id, $starts, $ends, $files){
$values = "('$starts', '$ends', $show_id, 0, 0, NULL, NULL, TIMESTAMP '$ends' - TIMESTAMP '$starts', '$now', 'f')";
$query = "INSERT INTO cc_show_instances $columns values $values ";
echo $query.PHP_EOL;
$result = query($conn, $query);
$query = "SELECT currval('cc_show_instances_id_seq');";
@ -92,7 +92,7 @@ function insertIntoCcShowInstances($conn, $show_id, $starts, $ends, $files){
while ($row = pg_fetch_array($result)) {
$show_instance_id = $row["currval"];
}
return $show_instance_id;
}
@ -102,9 +102,9 @@ function insertIntoCcShowInstances($conn, $show_id, $starts, $ends, $files){
*/
function insertIntoCcSchedule($conn, $files, $show_instance_id, $p_starts, $p_ends){
$columns = "(starts, ends, file_id, clip_length, fade_in, fade_out, cue_in, cue_out, media_item_played, instance_id)";
$starts = $p_starts;
foreach($files as $file){
$endsDateTime = new DateTime($starts, new DateTimeZone("UTC"));
@ -115,9 +115,9 @@ function insertIntoCcSchedule($conn, $files, $show_instance_id, $p_starts, $p_en
$values = "('$starts', '$ends', $file[id], '$file[length]', '00:00:00', '00:00:00', '00:00:00', '$file[length]', 'f', $show_instance_id)";
$query = "INSERT INTO cc_schedule $columns VALUES $values";
echo $query.PHP_EOL;
$starts = $ends;
$result = query($conn, $query);
$result = query($conn, $query);
}
}
@ -131,7 +131,7 @@ function getEndTime($startDateTime, $p_files){
foreach ($p_files as $file){
$startDateTime->add(getDateInterval($file['length']));
}
return $startDateTime;
}
@ -142,7 +142,7 @@ function rabbitMqNotify(){
echo "Contacting $url".PHP_EOL;
$ch = curl_init($url);
curl_exec($ch);
curl_close($ch);
curl_close($ch);
}
$conn = pg_connect("host=localhost port=5432 dbname=airtime user=airtime password=airtime");
@ -152,9 +152,9 @@ if (!$conn) {
}
if (count($argv) > 1){
if ($argv[1] == "--clean"){
if ($argv[1] == "--clean"){
$tables = array("cc_schedule", "cc_show_instances", "cc_show");
foreach($tables as $table){
$query = "DELETE FROM $table";
echo $query.PHP_EOL;
@ -162,9 +162,9 @@ if (count($argv) > 1){
}
rabbitMqNotify();
exit(0);
} else {
} else {
$str = <<<EOD
This script schedules a file to play 30 seconds in the future. It
This script schedules a file to play 30 seconds in the future. It
modifies the database tables cc_schedule, cc_show_instances and cc_show.
You can clean up these tables using the --clean option.
EOD;
@ -178,7 +178,7 @@ $startDateTime = new DateTime("now + 30sec", new DateTimeZone("UTC"));
$starts = $startDateTime->format("Y-m-d H:i:s");
//$ends = $endDateTime->format("Y-m-d H:i:s");
$files = getFileFromCcFiles($conn);
$files = getFileFromCcFiles($conn);
$show_id = insertIntoCcShow($conn);
$endDateTime = getEndTime(clone $startDateTime, $files);

View File

@ -5,5 +5,3 @@ cp scripts/git-merge-po /usr/local/bin
chmod +x /usr/local/bin/git-merge-po
cat scripts/git-config-git-merge-po >> ../.git/config
cat scripts/git-attributes-git-merge-po >> ../.gitattributes

View File

@ -1,12 +1,12 @@
import logging
import os
import time
import shutil
import sys
import logging
import time
from subprocess import PIPE, Popen
from configobj import ConfigObj
from subprocess import Popen, PIPE
from api_clients import api_client as apc
from configobj import ConfigObj
"""
The purpose of this script is that you can run it, and it will compare what the database has to what your filesystem
@ -15,8 +15,8 @@ similar code when it starts up (but then makes changes if something is different
"""
class AirtimeMediaMonitorBootstrap():
class AirtimeMediaMonitorBootstrap:
"""AirtimeMediaMonitorBootstrap constructor
Keyword Arguments:
@ -24,36 +24,38 @@ class AirtimeMediaMonitorBootstrap():
pe -- reference to an instance of ProcessEvent
api_clients -- reference of api_clients to communicate with airtime-server
"""
def __init__(self):
config = ConfigObj('/etc/airtime/airtime.conf')
config = ConfigObj("/etc/airtime/airtime.conf")
self.api_client = apc.api_client_factory(config)
"""
"""
try:
logging.config.fileConfig("logging.cfg")
except Exception, e:
print 'Error configuring logging: ', e
sys.exit(1)
"""
self.logger = logging.getLogger()
self.logger.info("Adding %s on watch list...", "xxx")
self.scan()
"""On bootup we want to scan all directories and look for files that
weren't there or files that changed before media-monitor process
went offline.
"""
def scan(self):
directories = self.get_list_of_watched_dirs();
directories = self.get_list_of_watched_dirs()
self.logger.info("watched directories found: %s", directories)
for id, dir in directories.iteritems():
self.logger.debug("%s, %s", id, dir)
#CHANGED!!!
#self.sync_database_to_filesystem(id, api_client.encode_to(dir, "utf-8"))
# CHANGED!!!
# self.sync_database_to_filesystem(id, api_client.encode_to(dir, "utf-8"))
self.sync_database_to_filesystem(id, dir)
"""Gets a list of files that the Airtime database knows for a specific directory.
@ -61,6 +63,7 @@ class AirtimeMediaMonitorBootstrap():
get_list_of_watched_dirs function.
dir_id -- row id of the directory in the cc_watched_dirs database table
"""
def list_db_files(self, dir_id):
return self.api_client.list_all_db_files(dir_id)
@ -68,23 +71,29 @@ class AirtimeMediaMonitorBootstrap():
returns the path and the database row id for this path for all watched directories. Also
returns the Stor directory, which can be identified by its row id (always has value of "1")
"""
def get_list_of_watched_dirs(self):
json = self.api_client.list_all_watched_dirs()
return json["dirs"]
def scan_dir_for_existing_files(self, dir):
command = 'find "%s" -type f -iname "*.ogg" -o -iname "*.mp3" -readable' % dir.replace('"', '\\"')
command = (
'find "%s" -type f -iname "*.ogg" -o -iname "*.mp3" -readable'
% dir.replace('"', '\\"')
)
self.logger.debug(command)
#CHANGED!!
# CHANGED!!
stdout = self.exec_command(command).decode("UTF-8")
return stdout.splitlines()
def exec_command(self, command):
p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE)
stdout, stderr = p.communicate()
if p.returncode != 0:
self.logger.warn("command \n%s\n return with a non-zero return value", command)
self.logger.warn(
"command \n%s\n return with a non-zero return value", command
)
self.logger.error(stderr)
return stdout
@ -93,10 +102,11 @@ class AirtimeMediaMonitorBootstrap():
and reads the list of files in the local file system. Its purpose is to discover which files
exist on the file system but not in the database and vice versa, as well as which files have
been modified since the database was last updated. In each case, this method will call an
appropiate method to ensure that the database actually represents the filesystem.
appropriate method to ensure that the database actually represents the filesystem.
dir_id -- row id of the directory in the cc_watched_dirs database table
dir -- pathname of the directory
"""
def sync_database_to_filesystem(self, dir_id, dir):
"""
set to hold new and/or modified files. We use a set to make it ok if files are added
@ -106,7 +116,7 @@ class AirtimeMediaMonitorBootstrap():
db_known_files_set = set()
files = self.list_db_files(dir_id)
for file in files['files']:
for file in files["files"]:
db_known_files_set.add(file)
existing_files = self.scan_dir_for_existing_files(dir)
@ -114,18 +124,17 @@ class AirtimeMediaMonitorBootstrap():
existing_files_set = set()
for file_path in existing_files:
if len(file_path.strip(" \n")) > 0:
existing_files_set.add(file_path[len(dir):])
existing_files_set.add(file_path[len(dir) :])
deleted_files_set = db_known_files_set - existing_files_set
new_files_set = existing_files_set - db_known_files_set
print("DB Known files: \n%s\n\n" % len(db_known_files_set))
print("FS Known files: \n%s\n\n" % len(existing_files_set))
print("Deleted files: \n%s\n\n" % deleted_files_set)
print("New files: \n%s\n\n" % new_files_set)
print ("DB Known files: \n%s\n\n"%len(db_known_files_set))
print ("FS Known files: \n%s\n\n"%len(existing_files_set))
print ("Deleted files: \n%s\n\n"%deleted_files_set)
print ("New files: \n%s\n\n"%new_files_set)
if __name__ == "__main__":
AirtimeMediaMonitorBootstrap()

View File

@ -90,7 +90,7 @@ fi
rm -rf liquidsoap-full
git clone https://github.com/savonet/liquidsoap-full
cd liquidsoap-full
git checkout master
git checkout master
make init
make update

View File

@ -26,7 +26,7 @@ build_env () {
echo "Please use -u to assign sudo username before build environments."
exit 1
fi
echo "build_env $1"
#exec > >(tee ./liquidsoap_compile_logs/build_env_$1.log)
os=`echo $1 | awk '/(debian)/'`
@ -40,7 +40,7 @@ build_env () {
useradd tmp
echo "User tmp is created."
fi
apt-get update
apt-get --force-yes -y install debootstrap dchroot
echo [$1] > /etc/schroot/chroot.d/$1.conf
@ -87,7 +87,7 @@ compile_liq () {
else
mv ./liquidsoap-compile_logs/compile_liq_$1.log ./liquidsoap-compile_logs/fail_to_compile_liq_$1.log
fi
}
}
os_versions=("ubuntu_lucid_32" "ubuntu_lucid_64" "ubuntu_precise_32" "ubuntu_precise_64" "ubuntu_quantal_32" "ubuntu_quantal_64" "ubuntu_raring_32" "ubuntu_raring_64" "debian_squeeze_32" "debian_squeeze_64" "debian_wheezy_32" "debian_wheezy_64")
@ -147,7 +147,7 @@ do
compile_liq ${os_versions[$i]} | tee ./liquidsoap-compile_logs/compile_liq_${os_versions[$i]}.log
flag=0
fi
done
done
if [ $flag = 1 ];then
echo "Unsupported Platform from:"
for k in "${os_versions[@]}"
@ -167,4 +167,3 @@ do
;;
esac
done

View File

@ -74,7 +74,7 @@ tar -czf $target_file \
--exclude dev_tools \
--exclude vendor/phing \
--exclude vendor/simplepie/simplepie/tests \
libretime-${suffix}
libretime-${suffix}
echo " Done"
popd

View File

@ -1,5 +1,3 @@
[merge "pofile"]
name = Gettext merge driver
driver = git merge-po %O %A %B

12
dev_tools/scripts/git-merge-po Normal file → Executable file
View File

@ -1,6 +1,6 @@
#!/bin/sh
#
# https://gist.github.com/mezis/1605647
# https://gist.github.com/mezis/1605647
# by Julien Letessier (mezis)
#
# Custom Git merge driver - merges PO files using msgcat(1)
@ -8,20 +8,20 @@
# - Install gettext
#
# - Place this script in your PATH
#
#
# - Add this to your .git/config :
#
# [merge "pofile"]
# name = Gettext merge driver
# driver = git merge-po %O %A %B
#
#
# - Add this to .gitattributes :
#
#
# *.po merge=pofile
# *.pot merge=pofile
#
# - When merging branches, conflicts in PO files will be maked with "#-#-#-#"
#
# - When merging branches, conflicts in PO files will be marked with "#-#-#-#"
#
O=$1
A=$2
B=$3

View File

@ -5,7 +5,7 @@ if [[ $EUID -ne 0 ]]; then
fi
usage () {
echo "Use --enable <user> or --disable flag. Enable is to set up environment"
echo "Use --enable <user> or --disable flag. Enable is to set up environment"
echo "for specified user. --disable is to reset it back to pypo user"
}
@ -28,7 +28,7 @@ elif [ "$1" = "--disable" ]; then
echo "Changing ownership to user $1"
chmod 644 /etc/airtime/airtime.conf
chown -Rv $user:$user /var/tmp/airtime/pypo/
chmod -v a+r /etc/airtime/api_client.cfg
chmod -v a+r /etc/airtime/api_client.cfg
/etc/init.d/airtime-playout stop-liquidsoap

View File

@ -8,4 +8,4 @@ photocredit: Top photo by <a href="https://unsplash.com/@shotaspot?utm_source=un
# DEAD AIR
Sorry... there's nothing there.
Sorry... there's nothing there.

View File

@ -1 +1 @@
libretime.org
libretime.org

View File

@ -1,4 +1,4 @@
title: LibreTime
title: LibreTime
description: An open source radio automation server made for (and by) low-power FM stations and the rest of us.
logo: 144px.png
baseurl: "" # The subpath of your site, e.g. /blog
@ -9,7 +9,7 @@ includes_dir: _includes
favicon: favicon.ico
exclude: ['README']
exclude: ["README"]
# Collections Settings
collections:
@ -20,4 +20,3 @@ collections:
# Build settings
plugins:
- kramdown

View File

@ -4,7 +4,7 @@ title: LibreTime API Usage
category: dev
---
The LibreTime API enables many types of information about the broadcast schedule and configuration to be retrieved from the LibreTime server. Other than the live-info and week-info data fetched by website widgets (see the chapter *Exporting the schedule*), all API requests must be authenticated using the secret API key stored in the file */etc/airtime/api\_client.cfg* on the LibreTime server. This key is autogenerated during LibreTime installation and should be unique for each server.
The LibreTime API enables many types of information about the broadcast schedule and configuration to be retrieved from the LibreTime server. Other than the live-info and week-info data fetched by website widgets (see the chapter _Exporting the schedule_), all API requests must be authenticated using the secret API key stored in the file _/etc/airtime/api_client.cfg_ on the LibreTime server. This key is autogenerated during LibreTime installation and should be unique for each server.
If you intend to use the LibreTime API across a public network, for security reasons it is highly recommended that all API requests are sent over encrypted https: and that the web server is configured to accept requests to the api/ directory from specific host names or IP addresses only.
@ -14,14 +14,14 @@ The format of API requests is:
where api-action is the type of request and XXXXXX is the secret API key. Available actions include:
- on-air-light - return true if the station is on air
- status - get the status of LibreTime components and resource usage
- version - returns the version of LibreTime installed
- get-files-without-silan-value - list files for which silence detection has not yet been performed
- get-stream-setting - gets the settings of LibreTime output streams
- get-stream-parameters - gets the parameters of LibreTime output streams
- on-air-light - return true if the station is on air
- status - get the status of LibreTime components and resource usage
- version - returns the version of LibreTime installed
- get-files-without-silan-value - list files for which silence detection has not yet been performed
- get-stream-setting - gets the settings of LibreTime output streams
- get-stream-parameters - gets the parameters of LibreTime output streams
For example, using the action *get-stream-setting* returns the following output for the first configured stream:
For example, using the action _get-stream-setting_ returns the following output for the first configured stream:
{"keyname":"s1_type","value":"ogg","type":"string"},
@ -38,12 +38,12 @@ For example, using the action *get-stream-setting* returns the following output
{"keyname":"s1_genre","value":"Screamo","type":"string"},
which is enough information to construct a player widget dynamically. (s1\_url is the station's homepage, not the stream URL). The same information is provided with an s2\_ prefix for the second stream, and s3\_ prefix for the third stream.
which is enough information to construct a player widget dynamically. (s1_url is the station's homepage, not the stream URL). The same information is provided with an s2\_ prefix for the second stream, and s3\_ prefix for the third stream.
Some API requests require the directory ID number to be specified as *dir\_id* including:
Some API requests require the directory ID number to be specified as _dir_id_ including:
- list-all-files - list files in the specified directory
- get-files-without-replay-gain - list files in the specified directory for which ReplayGain has not been calculated yet
- list-all-files - list files in the specified directory
- get-files-without-replay-gain - list files in the specified directory for which ReplayGain has not been calculated yet
For example, using a request such as:

View File

@ -5,11 +5,11 @@ category: admin
---
> At the moment, there is not a way to automatically restore a Libretime backup.
> To restore a failed Libretime instance, install a fresh copy, go through the
> standard setup process, and reupload the backed-up media files. A *Watched Folders*
> To restore a failed Libretime instance, install a fresh copy, go through the
> standard setup process, and reupload the backed-up media files. A _Watched Folders_
> feature is [currently in development](https://github.com/LibreTime/libretime/issues/70).
A backup script is supplied for your convenience in the *utils/* folder of the Libretime repo.
A backup script is supplied for your convenience in the _utils/_ folder of the Libretime repo.
Run it using:
```
@ -27,14 +27,14 @@ crontab with `sudo crontab -e`:
> For more information on how Cron works, check out [this Redhat guide](https://www.redhat.com/sysadmin/automate-linux-tasks-cron).
If you wish to deploy your own backup solution, the following files and folders need to
If you wish to deploy your own backup solution, the following files and folders need to
be backed up.
```
/srv
/airtime
/stor
/imported - Sucessfully imported media
/imported - Successfully imported media
/organize - A temporary holding place for uploaded media as the importer works
/etc
/airtime
@ -43,11 +43,11 @@ be backed up.
liquidsoap.cfg - The main configuration file for Liquidsoap
```
In addition, you should keep a copy of the database current to the backup. The below code
In addition, you should keep a copy of the database current to the backup. The below code
can be used to export the Libretime database to a file.
```
sudo -u postgres pg_dumpall filename
sudo -u postgres pg_dumpall filename
# or to a zipped archive
sudo -u postgres pg_dumpall | gzip -c > archivename.gz
```
@ -58,9 +58,8 @@ the backup server also contains an LibreTime installation, it should be possible
to switch playout to this second machine relatively quickly, in case of a
hardware failure or other emergency on the production server.)
Two notible backup tools are [rsync](http://rsync.samba.org/) (without version control) and
[rdiff-backup](http://www.nongnu.org/rdiff-backup/) (with version control). *rsync* comes
Two notible backup tools are [rsync](http://rsync.samba.org/) (without version control) and
[rdiff-backup](http://www.nongnu.org/rdiff-backup/) (with version control). _rsync_ comes
preinstalled with Ubuntu Server.
> **Note:** Standard *rsync* backups, which are used by the backup script, cannot restore files deleted in the backup itself
> **Note:** Standard _rsync_ backups, which are used by the backup script, cannot restore files deleted in the backup itself

View File

@ -30,7 +30,7 @@ To add content to a show, click the show in any view on the Calendar, and select
![](/img/Screenshot488-Add_remove_content.png)
The **Schedule Tracks** action opens a window with the name of the show. Like when using the **Now Playing** page, you can search for content items and add them to the show schedule on the right side of the page. Refer to the *Now Playing* chapter for details.
The **Schedule Tracks** action opens a window with the name of the show. Like when using the **Now Playing** page, you can search for content items and add them to the show schedule on the right side of the page. Refer to the _Now Playing_ chapter for details.
When your show has all the required content, click the **OK** button in the bottom right corner to close the window. Back in the **Calendar**, click the show and select **View** from the pop-up menu to view a list of content now included in the show.
@ -42,7 +42,7 @@ The **Contents of Show** window is a read-only interface featuring an orange bar
### Removing content from a show
To remove an individual item from a show, click on the show in the **Calendar**, and select **Schedule Tracks** from the pop-up menu. In the window which opens, click any item you wish to remove from the show, then click **Delete** on the pop-up menu, or check the box in the item's row then click the **Remove** icon at the top of the table. To remove all files and playlists from a show, click on the show in the **Calendar**, and select **Clear Show** from the pop-up menu. 
To remove an individual item from a show, click on the show in the **Calendar**, and select **Schedule Tracks** from the pop-up menu. In the window which opens, click any item you wish to remove from the show, then click **Delete** on the pop-up menu, or check the box in the item's row then click the **Remove** icon at the top of the table. To remove all files and playlists from a show, click on the show in the **Calendar**, and select **Clear Show** from the pop-up menu.
### Deleting an upcoming show
@ -54,4 +54,4 @@ You cannot delete or remove content from shows that have already played out. The
### Cancelling playout
If you wish to cancel playout of a show while it is running, click on the show in the **Calendar** and select **Cancel Show** from the pop-up menu. This action cannot be undone.
If you wish to cancel playout of a show while it is running, click on the show in the **Calendar** and select **Cancel Show** from the pop-up menu. This action cannot be undone.

View File

@ -6,7 +6,7 @@ permalink: /contribute
---
> LibreTime is a fork of AirTime due to stalled development of the open source version. For background on this,
see this [open letter to the Airtime community](https://gist.github.com/hairmare/8c03b69c9accc90cfe31fd7e77c3b07d).
> see this [open letter to the Airtime community](https://gist.github.com/hairmare/8c03b69c9accc90cfe31fd7e77c3b07d).
## Bug reporting
@ -28,7 +28,7 @@ supported? Follow [this guide](/docs/interface-localization) to add your languag
## Write documentation
Our site is now built by Jekyll, which has an installation guide [here](https://jekyllrb.com/docs/installation/) to help get you started.
After cloning our repo locally, enter the `docs/` directory and run
After cloning our repo locally, enter the `docs/` directory and run
```
bundle install
@ -37,7 +37,7 @@ jekyll serve
## Code
Are you familar with coding in PHP? Have you made projects in Liquidsoap and some of the other services we use?
Are you familiar with coding in PHP? 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 **Code** page, 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.
@ -47,14 +47,15 @@ will suit you well, use the links for a quick 101.
## 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 virtural machine on your local system or in a cloud VM. We have instructions for setting up a virtural
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 persistant install.
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](http://propelorm.org) to interact with the ZendPHP components and create the database.
@ -63,4 +64,4 @@ If you are a developer seeking to add new columns to the database here are the s
1. Modify `airtime_mvc/build/schema.xml` with any changes.
2. Run `dev_tools/propel_generate.sh`
3. Update the upgrade.sql under `airtime_mvc/application/controllers/upgrade_sql/VERSION` for example
`ALTER TABLE imported_podcast ADD COLUMN album_override boolean default 'f' NOT NULL;`
`ALTER TABLE imported_podcast ADD COLUMN album_override boolean default 'f' NOT NULL;`

View File

@ -28,7 +28,7 @@ the name of the show from the drop-down menu which will appear.
![](/img/filter.png)
On the left side of the page, the **Start** and **End** times, **Duration**
On the left side of the page, the **Start** and **End** times, **Duration**
and **Title** of each content item are shown. On the right, **Creator**,
**Album**, **Cue** or **Fade** times and **Mime** type (file format) can also be
shown. This information can help you to prepare voice tracks for insertion into
@ -103,11 +103,11 @@ use the checkboxes on the left side of the library table to select specific
items. Then drag one of the items into the show to add all of the selected
items, or click the **Add to selected show** button, which has a plus icon. If
you wish, you can also use the **Trashcan** button to permanently remove items
from LibreTime's library. Only *admin* users have permission to delete all
from LibreTime's library. Only _admin_ users have permission to delete all
items.
To insert checkbox selected items at a specific time in the show schedule, click
one of the songs in the schedule table. Then click the **Add to selected show**
one of the songs in the schedule table. Then click the **Add to selected show**
button in the library table. This will insert the library songs after the
selected scheduled song.

View File

@ -12,7 +12,7 @@ To change the password of the current user:
2. Click on the username in the upper right corner (next to Log Out)
3. Enter the new password twice and click **Save**
To change the password for a different user (requires *Administrator* privileges):
To change the password for a different user (requires _Administrator_ privileges):
1. Log in to Libretime
2. Go to **Settings** > **Manage Users**
@ -20,17 +20,17 @@ To change the password for a different user (requires *Administrator* privileges
### PostgreSQL
Two of the most important passwords that should be changed *immediately* after installation
Two of the most important passwords that should be changed _immediately_ after installation
are the passwords used by the PostgreSQL database.
It is strongly recommended that you do this before exposing your server to the internet beyond your internal network.
1. Login to PostgreSQL with `sudo -u postgres psql`. The PostgreSQL shell - `postgres=#` - means that you have logged in successfully.
2. Change the admin password with `ALTER USER postgres PASSWORD 'myPassword';`, where `myPassword` is the new password.
Make sure to include the semicolon at the end! A response of `ALTER ROLE` means that the command ran successfully.
3. Change the password for the *airtime* user with `ALTER USER airtime WITH PASSWORD 'new_password';`
A response of `ALTER ROLE` means that the command ran successfully.
4. If all is successful, logout of PostgreSQL with `\q`, go back to */etc/airtime/airtime.conf* to edit the password
in the config file, and restart all services mentioned in the previous section.
Make sure to include the semicolon at the end! A response of `ALTER ROLE` means that the command ran successfully.
3. Change the password for the _airtime_ user with `ALTER USER airtime WITH PASSWORD 'new_password';`
A response of `ALTER ROLE` means that the command ran successfully.
4. If all is successful, logout of PostgreSQL with `\q`, go back to _/etc/airtime/airtime.conf_ to edit the password
in the config file, and restart all services mentioned in the previous section.
### Icecast
@ -38,7 +38,7 @@ Random passwords are generated for Icecast during the installation. To look up a
`/etc/icecast2/icecast.xml`
Replace the admin and *changeme* fields below.
Replace the admin and _changeme_ fields below.
```
<authentication>
@ -68,4 +68,4 @@ To change the default password for Rabbitmq, run the following command
sudo rabbitmqctl change_password airtime newpassword
```
and then update the `/etc/airtime/airtime.conf` file with the new password.
and then update the `/etc/airtime/airtime.conf` file with the new password.

View File

@ -56,11 +56,11 @@ LibreTime needs direct access to LDAP so it can fetch additional information. It
a [system account](https://www.freeipa.org/page/HowTo/LDAP#System_Accounts) that you need to
set up beforehand.
You can configure everything pertaining to how LibreTime accesses LDAP in
You can configure everything pertaining to how LibreTime accesses LDAP in
`/etc/airtime/airtime.conf`. The default file has the following values you need to change.
```ini
#
#
# ----------------------------------------------------------------------
# L D A P
# ----------------------------------------------------------------------

View File

@ -5,7 +5,7 @@ git: hd-audio-modules.md
category: admin
---
This listing is provided to help ensure that the correct model parameter is passed to the ALSA kernel module for an Intel HDA soundcard, if one is fitted to your LibreTime server. See the chapter *Preparing the server* in this book for more details.
This listing is provided to help ensure that the correct model parameter is passed to the ALSA kernel module for an Intel HDA soundcard, if one is fitted to your LibreTime server. See the chapter _Preparing the server_ in this book for more details.
```
Model name Description
@ -155,7 +155,7 @@ Conexant 5045
Conexant 5047
=============
laptop Basic Laptop config
laptop Basic Laptop config
laptop-hp Laptop config for some HP models (subdevice 30A5)
laptop-eapd Laptop config with EAPD support
test for testing/debugging purpose, almost all controls
@ -316,4 +316,4 @@ Cirrus Logic CS4208
VIA VT17xx/VT18xx/VT20xx
========================
auto BIOS setup (default)
```
```

View File

@ -4,15 +4,15 @@ layout: article
category: install
---
The streaming host configuration for LibreTime is shown in the file */etc/airtime/liquidsoap.cfg* which is automatically generated by the **Streams** page, found on the **System** menu of the LibreTime administration interface. For this reason, you would not normally edit the streaming configuration manually, as any changes are likely to be overwritten by the administration interface.
The streaming host configuration for LibreTime is shown in the file _/etc/airtime/liquidsoap.cfg_ which is automatically generated by the **Streams** page, found on the **System** menu of the LibreTime administration interface. For this reason, you would not normally edit the streaming configuration manually, as any changes are likely to be overwritten by the administration interface.
## Database and RabbitMQ hosts {#database}
Optionally, you may wish to edit the file */etc/airtime/airtime.conf* to set the PostgreSQL database host, and the username and password to connect to the database with:
Optionally, you may wish to edit the file _/etc/airtime/airtime.conf_ to set the PostgreSQL database host, and the username and password to connect to the database with:
sudo nano /etc/airtime/airtime.conf
You can also set options for RabbitMQ messaging and the LibreTime server in this file, although you should not normally need to adjust the defaults unless you are running a large LibreTime system distributed across multiple servers. To run the LibreTime server in demo mode, which changes the greeting on the login page and prevents user accounts from being created or modified, set the value of *demo* to 1.
You can also set options for RabbitMQ messaging and the LibreTime server in this file, although you should not normally need to adjust the defaults unless you are running a large LibreTime system distributed across multiple servers. To run the LibreTime server in demo mode, which changes the greeting on the login page and prevents user accounts from being created or modified, set the value of _demo_ to 1.
[database]
host = localhost
@ -43,7 +43,7 @@ You can also set options for RabbitMQ messaging and the LibreTime server in this
[demo]
demo = 0
Save and close the file with **Ctrl+O** and **Ctrl+X**. In order to update the configuration
Save and close the file with **Ctrl+O** and **Ctrl+X**. In order to update the configuration
used by the various components of LibreTime, run the following commands
sudo systemctl restart libretime-liquidsoap
@ -53,11 +53,11 @@ used by the various components of LibreTime, run the following commands
## API client configuration {#api}
The LibreTime API enables many types of information about the broadcast schedule and configuration to be retrieved from the LibreTime server. Other than the live-info and week-info data fetched by website widgets (see the chapter *Exporting the schedule*), all API requests must be authenticated using the secret API key stored in the file */etc/airtime/api\_client.cfg* on the LibreTime server. This key is autogenerated during LibreTime installation and should be unique for each server.
The LibreTime API enables many types of information about the broadcast schedule and configuration to be retrieved from the LibreTime server. Other than the live-info and week-info data fetched by website widgets (see the chapter _Exporting the schedule_), all API requests must be authenticated using the secret API key stored in the file _/etc/airtime/api_client.cfg_ on the LibreTime server. This key is autogenerated during LibreTime installation and should be unique for each server.
If you intend to use the LibreTime API across a public network, for security reasons it is highly recommended that all API requests are sent over encrypted https: and that the web server is configured to accept requests to the api/ directory from specific host names or IP addresses only.
If you have changed the *base\_url*, *base\_port* or *base\_dir* setting in */etc/airtime/airtime.conf* from the defaults, you will probably also have to update the *Hostname* settings in the file */etc/airtime/api\_client.cfg* accordingly.**
If you have changed the _base_url_, _base_port_ or _base_dir_ setting in _/etc/airtime/airtime.conf_ from the defaults, you will probably also have to update the _Hostname_ settings in the file _/etc/airtime/api_client.cfg_ accordingly.\*\*
bin_dir = /usr/lib/airtime/api_clients
api_key = 'XXXXXXXXXXXXXXXXXXXX'
@ -68,7 +68,7 @@ If you have changed the *base\_url*, *base\_port* or *base\_dir* setting in */et
## Apache max file size configuration {#apache}
By default, the maximum upload file size is 40 MB, which may not be large enough for some stations, especially if they are uploading prerecorded shows. The setting for this is located in */etc/apache2/sites-available/airtime.config*. Search for and update the following in megabytes:
By default, the maximum upload file size is 40 MB, which may not be large enough for some stations, especially if they are uploading prerecorded shows. The setting for this is located in _/etc/apache2/sites-available/airtime.config_. Search for and update the following in megabytes:
```
; Maximum allowed size for uploaded files.
@ -78,7 +78,7 @@ upload_max_filesize = 40M
post_max_size = 40M
```
For quick reference, 1024 MB = 1 GB and 2048 MB = 2 GB, but most will be okay with rounding to the nearest thousand. After updating the config file, restart Apache.
For quick reference, 1024 MB = 1 GB and 2048 MB = 2 GB, but most will be okay with rounding to the nearest thousand. After updating the config file, restart Apache.
```
sudo systemctl restart apache2
@ -86,7 +86,7 @@ sudo systemctl restart apache2
## Playout settings {#playout}
Settings for pypo, the playout engine used by LibreTime, are found in the file */etc/airtime/airtime.conf*. After making changes to this file, you will have to issue the command:
Settings for pypo, the playout engine used by LibreTime, are found in the file _/etc/airtime/airtime.conf_. After making changes to this file, you will have to issue the command:
sudo systemctl restart libretime-playout
@ -160,7 +160,7 @@ for the changes to take effect.
# while 'otf' (on the fly) cues while loading into ls
# (needs the post_processor patch)
cue_style = pre
## RabbitMQ hostname changes
If the Airtime logs indicate failures to connect to the RabbitMQ server, such as:
@ -171,9 +171,10 @@ If the Airtime logs indicate failures to connect to the RabbitMQ server, such as
2013-10-31 08:21:11,255 ERROR - \[pypomessagehandler.py : main() : line 99\] - Error connecting to RabbitMQ Server. Trying again in few seconds - See more at: http://forum.sourcefabric.org/discussion/16050/\#sthash.W8OJrNFm.dpuf
```
but the RabbitMQ server is running normally, this error might be due to a change in the server's hostname since Libretime installation. Directory names under */var/lib/rabbitmq/mnesia/* indicate that RabbitMQ's database files are organised according to the hostname of the server (ex. `rabbit@airtime`) where the hostname is *airtime.example.com*. If the hostname has changed, it may be necessary to reconfigure RabbitMQ manually, as follows:
1. Delete the files in */var/lib/rabbitmq/mnesia/*
but the RabbitMQ server is running normally, this error might be due to a change in the server's hostname since Libretime installation. Directory names under _/var/lib/rabbitmq/mnesia/_ indicate that RabbitMQ's database files are organised according to the hostname of the server (ex. `rabbit@airtime`) where the hostname is _airtime.example.com_. If the hostname has changed, it may be necessary to reconfigure RabbitMQ manually, as follows:
1. Delete the files in _/var/lib/rabbitmq/mnesia/_
```
sudo rm -r /var/lib/rabbitmq/mnesia/*
@ -185,13 +186,13 @@ sudo rm -r /var/lib/rabbitmq/mnesia/*
sudo systemctl restart rabbitmq-server
```
3. Enter the following commands to set up authentication and grant permissions. The *rabbitmqctl add\_user* command requires the RabbitMQ password from the /etc/airtime/airtime.conf file as an argument. The *rabbitmqctl set\_permissions* command should be entered on one line, with the list of Airtime services repeated three times:
3. Enter the following commands to set up authentication and grant permissions. The _rabbitmqctl add_user_ command requires the RabbitMQ password from the /etc/airtime/airtime.conf file as an argument. The _rabbitmqctl set_permissions_ command should be entered on one line, with the list of Airtime services repeated three times:
```
rabbitmqctl add_vhost /airtime
rabbitmqctl add_user airtime XXXXXXXXXXXXXXXXXXXX
rabbitmqctl set_permissions -p /airtime airtime
"airtime-pypo|pypo-fetch|airtime-analyzer|media-monitor"
  "airtime-pypo|pypo-fetch|airtime-analyzer|media-monitor"
 "airtime-pypo|pypo-fetch|airtime-analyzer|media-monitor"
```
"airtime-pypo|pypo-fetch|airtime-analyzer|media-monitor"
"airtime-pypo|pypo-fetch|airtime-analyzer|media-monitor"
```

View File

@ -8,7 +8,7 @@ category: admin
LibreTime supports direct connection to two popular streaming media servers, the open source **Icecast** (<http://www.icecast.org>) and the proprietary **SHOUTcast** (<http://www.shoutcast.com>). Apart from the software license, the main difference between these two servers is that Icecast supports simultaneous MP3, AAC, Ogg Vorbis or Ogg Opus streaming from LibreTime, whereas SHOUTcast supports MP3 and AAC streams but not Ogg Vorbis or Opus. The royalty-free Ogg Vorbis format has the advantage of better sound quality than MP3 at lower bitrates, which has a direct impact on the amount of bandwidth that your station will require to serve the same number of listeners. Ogg Opus also benefits from good sound quality at low bitrates, with the added advantage of lower latency than other streaming formats. Opus is now an IETF standard (<http://tools.ietf.org/html/rfc6716>) and requires Icecast 2.4 or later to be installed on the streaming server.
Ogg Vorbis playback is supported in **Mozilla Firefox**, **Google Chrome** and **Opera** browsers, via **jPlayer** (<http://jplayer.org/>), and is also supported in several popular media players, including VideoLAN Client, also known as VLC (<http://www.videolan.org/vlc/>). (See the chapter *Stream player for your website* on how to deliver **jPlayer** to your audience). Ogg Opus is relatively new and is supported natively in the very latest browsers, such as Mozilla Firefox 25.0, and media players including VLC 2.0.4 or later.
Ogg Vorbis playback is supported in **Mozilla Firefox**, **Google Chrome** and **Opera** browsers, via **jPlayer** (<http://jplayer.org/>), and is also supported in several popular media players, including VideoLAN Client, also known as VLC (<http://www.videolan.org/vlc/>). (See the chapter _Stream player for your website_ on how to deliver **jPlayer** to your audience). Ogg Opus is relatively new and is supported natively in the very latest browsers, such as Mozilla Firefox 25.0, and media players including VLC 2.0.4 or later.
Streaming MP3 below a bitrate of 128kbps is not recommended for music, because of a perceptible loss of high audio frequencies in the broadcast playout. A 96kbps or 64kbps MP3 stream may be acceptable for voice broadcasts if there is a requirement for compatibility with legacy hardware playback devices which do not support Ogg Vorbis or Opus streams.
@ -18,18 +18,18 @@ Conversely, you may have a music station which wants to stream at 160kbps or 192
## UTF-8 metadata in Icecast MP3 streams
When sending metadata about your stream to an Icecast server in non-Latin alphabets, you may find that Icecast does not display the characters correctly for an MP3 stream, even though they are displayed correctly for an Ogg Vorbis stream. In the following screenshot, Russian characters are being displayed incorrectly in the *Current Song* field for the MP3 stream:
When sending metadata about your stream to an Icecast server in non-Latin alphabets, you may find that Icecast does not display the characters correctly for an MP3 stream, even though they are displayed correctly for an Ogg Vorbis stream. In the following screenshot, Russian characters are being displayed incorrectly in the _Current Song_ field for the MP3 stream:
![](/img/Screenshot223-Icecast_UTF-8_metadata.png)
The solution is to specify that the metadata for the MP3 mount point you are using should be interpreted using UTF-8 encoding. You can do this by adding the following stanza to the */etc/icecast2/icecast.xml* file, where *libretime.mp3* is the name of your mount point:
The solution is to specify that the metadata for the MP3 mount point you are using should be interpreted using UTF-8 encoding. You can do this by adding the following stanza to the _/etc/icecast2/icecast.xml_ file, where _libretime.mp3_ is the name of your mount point:
  <mount>
       <mount-name>/libretime.mp3</mount-name>
       <charset>UTF-8</charset>
  </mount>
<mount>
<mount-name>/libretime.mp3</mount-name>
<charset>UTF-8</charset>
</mount>
After saving the */etc/icecast2/icecast.xml* file, you should restart the Icecast server:
After saving the _/etc/icecast2/icecast.xml_ file, you should restart the Icecast server:
sudo invoke-rc.d icecast2 restart
Restarting icecast2: Starting icecast2
@ -38,17 +38,17 @@ After saving the */etc/icecast2/icecast.xml* file, you should restart the Icecas
## Icecast handover configuration
In a typical radio station configuration, the live output from the broadcast studio and the scheduled output from LibreTime are mixed together before being sent further along the broadcast chain, to a transmitter or streaming media server on the Internet. (This may not be the case if your LibreTime server is remote from the studio, and you are using the **Show Source Mount Point** or **Master Source Mount Point** to mix live and scheduled content. See the *Stream Settings* chapter for details).
In a typical radio station configuration, the live output from the broadcast studio and the scheduled output from LibreTime are mixed together before being sent further along the broadcast chain, to a transmitter or streaming media server on the Internet. (This may not be the case if your LibreTime server is remote from the studio, and you are using the **Show Source Mount Point** or **Master Source Mount Point** to mix live and scheduled content. See the _Stream Settings_ chapter for details).
If your **Icecast** server is hosted in a remote data centre, you may not have the option to handover the streaming media source manually, because you have no physical access to connect a broadcast mixer to the server. Disconnecting the stream and beginning another is less than ideal, because the audience's media players will also be disconnected when that happens.
The **Icecast** server has a *fallback-mount* feature which can be used to move clients (media players used by listeners or viewers) from one source to another, as new sources become available. This makes it possible to handover from LibreTime output to a show from another source, and handover to LibreTime again once the other show has ended.
The **Icecast** server has a _fallback-mount_ feature which can be used to move clients (media players used by listeners or viewers) from one source to another, as new sources become available. This makes it possible to handover from LibreTime output to a show from another source, and handover to LibreTime again once the other show has ended.
To enable fallback mounts, edit the main Icecast configuration file to define the mount points you will use, and the relationship between them.
sudo nano /etc/icecast2/icecast.xml
The example *<mount>* section provided in the *icecast.xml* file is commented out by default. Before or after the commented section, add three mount point definitions. The default mount point used by LibreTime is */airtime\_128* which is shown in the */etc/airtime/liquidsoap.cfg* file. You must also define a mount point for the live source (called */live.ogg* in this example) and a mount point for the public to connect to (called */stream.ogg* in this example).
The example _<mount>_ section provided in the _icecast.xml_ file is commented out by default. Before or after the commented section, add three mount point definitions. The default mount point used by LibreTime is _/airtime_128_ which is shown in the _/etc/airtime/liquidsoap.cfg_ file. You must also define a mount point for the live source (called _/live.ogg_ in this example) and a mount point for the public to connect to (called _/stream.ogg_ in this example).
<mount>
<mount-name>/airtime_128</mount-name>
@ -69,25 +69,25 @@ The example *<mount>* section provided in the *icecast.xml* file is commented ou
<hidden>0</hidden>
</mount>
These mount point definitions mean that a client connecting to a URL such as *http://icecast.example.com:8000/stream.ogg* will first fall back to the */live.ogg* mount point if it is available. If not, the client will fall back in turn to the */airtime\_128* mount point for LibreTime playout.
These mount point definitions mean that a client connecting to a URL such as *http://icecast.example.com:8000/stream.ogg* will first fall back to the _/live.ogg_ mount point if it is available. If not, the client will fall back in turn to the _/airtime_128_ mount point for LibreTime playout.
Setting the value of *<fallback-override>* to 1 (enabled) means that when the */live.ogg* mount point becomes available again, the client will be re-connected to it.  If you wish to hide the */airtime\_128* and */live.ogg* mount points from the public Icecast web interface, set the value of *<hidden>* in each of these definitions to 1.
Setting the value of _<fallback-override>_ to 1 (enabled) means that when the _/live.ogg_ mount point becomes available again, the client will be re-connected to it. If you wish to hide the _/airtime_128_ and _/live.ogg_ mount points from the public Icecast web interface, set the value of _<hidden>_ in each of these definitions to 1.
## Source configuration
Connect the other source to the Icecast server with the same parameters defined in the */etc/airtime/liquidsoap.cfg* file, except for the mount point. This should one of the mount points you have defined in the */etc/icecast2/icecast.xml* file, such as */live.ogg* in the example above.
Connect the other source to the Icecast server with the same parameters defined in the _/etc/airtime/liquidsoap.cfg_ file, except for the mount point. This should one of the mount points you have defined in the _/etc/icecast2/icecast.xml_ file, such as _/live.ogg_ in the example above.
To configure **Mixxx** for streaming to Icecast, click *Options*, *Preferences*, then *Live Broadcasting*. For server *Type*, select the default of *Icecast 2* when streaming to Debian or Ubuntu servers, as this is the current version of Icecast supplied with those GNU/Linux distributions.
To configure **Mixxx** for streaming to Icecast, click _Options_, _Preferences_, then _Live Broadcasting_. For server _Type_, select the default of _Icecast 2_ when streaming to Debian or Ubuntu servers, as this is the current version of Icecast supplied with those GNU/Linux distributions.
![](/img/Screenshot123-Mixxx_Preferences.png) 
![](/img/Screenshot123-Mixxx_Preferences.png)
By default, Icecast streams are buffered to guard against network problems, which causes latency for remote listeners. When monitoring the stream from a remote location, you may have to begin the live stream a few seconds before the previous stream ends to enable a smooth transition.
## Promoting your station through Icecast
If you have an Icecast server, you can put a link to the Icecast status page (by default at port 8000) on your station's homepage,
to provide an overview of available streams. See the chapter *Interface customization* for tips on theming the
Icecast status page. You can also use Now Playing widgets (see the chapter *Exporting the schedule*) or HTML5 stream players (see the chapter *Stream player for your website*) to help grow your audience.
to provide an overview of available streams. See the chapter _Interface customization_ for tips on theming the
Icecast status page. You can also use Now Playing widgets (see the chapter _Exporting the schedule_) or HTML5 stream players (see the chapter _Stream player for your website_) to help grow your audience.
On an Icecast server, you can uncomment the `<directory>` section in the _/etc/icecast2/icecast.xml_ file to have
your station automatically listed on the Icecast directory website <http://dir.xiph.org> which could help you pick
@ -100,14 +100,14 @@ up more listeners.
<yp-url>http://dir.xiph.org/cgi-bin/yp-cgi</yp-url>
</directory>
The Indymedia stream directory at <http://radio.indymedia.org/en/yp> links to grassroots independent radio projects around the world. You can add your station to their list with an additional *<directory>* section, as follows:
The Indymedia stream directory at <http://radio.indymedia.org/en/yp> links to grassroots independent radio projects around the world. You can add your station to their list with an additional _<directory>_ section, as follows:
<directory>
<yp-url-timeout>15</yp-url-timeout>
<yp-url>http://radio.indymedia.org/cgi-bin/yp-cgi</yp-url>
</directory>
Another stream directory service is provided by the Liquidsoap Flows! site <http://flows.liquidsoap.fm/>. The following section can be added to the file */usr/lib/airtime/pypo/bin/liquidsoap\_scripts/ls\_script.liq* after *add\_skip\_command(s)* on line 174, for a stream named '*ourstation*':
Another stream directory service is provided by the Liquidsoap Flows! site <http://flows.liquidsoap.fm/>. The following section can be added to the file _/usr/lib/airtime/pypo/bin/liquidsoap_scripts/ls_script.liq_ after _add_skip_command(s)_ on line 174, for a stream named '_ourstation_':
ourstation = register_flow(
radio="Rock 'n Roll Radio",
@ -119,4 +119,4 @@ Another stream directory service is provided by the Liquidsoap Flows! site <http
streams=[("ogg/128k","http://streaming.example.com/libretime_128")],
ourstation)
> **Note:** For the time being, a stream can be registered on the Liquidsoap Flows! site with any username and password. Authenticated services may be offered in future.
> **Note:** For the time being, a stream can be registered on the Liquidsoap Flows! site with any username and password. Authenticated services may be offered in future.

View File

@ -18,7 +18,7 @@ permalink: /install
- Wired internet connection and static IP address for on-prem install
[DigitalOcean](https://www.digitalocean.com/pricing/#Compute) and [Linode](https://www.linode.com/pricing/#row--compute)
have similar plans that meet Cloud Install requirements. Both plans cost $10/month.
have similar plans that meet Cloud Install requirements. Both plans cost $10/month.
## Preparing the server
@ -68,7 +68,6 @@ sudo ufw allow 8001,8002/tcp
<iframe width="560" height="315" src="https://www.youtube.com/embed/Djo_55LgjXE" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
Installing LibreTime consists of running the following commands in the terminal:
```

View File

@ -5,12 +5,11 @@ git: interface-customization.md
category: admin
---
The LibreTime administration interface, as a web application, is fully customizable using the same methods that you might use to modify a website. For instance, you may wish to increase certain font sizes or change the colours in the LibreTime interface to better suit staff users with impaired vision. To do so, open one of the CSS files in the */public/css/* directory under the LibreTime *DocumentRoot* directory in an editor such as **nano**:
The LibreTime administration interface, as a web application, is fully customizable using the same methods that you might use to modify a website. For instance, you may wish to increase certain font sizes or change the colours in the LibreTime interface to better suit staff users with impaired vision. To do so, open one of the CSS files in the _/public/css/_ directory under the LibreTime _DocumentRoot_ directory in an editor such as **nano**:
sudo nano /usr/share/airtime/public/css/styles.css
To change the background colour of the administration interface from dark gray to white, the *background:* property of the body tag could be changed to *\#ffffff* as follows:
To change the background colour of the administration interface from dark gray to white, the _background:_ property of the body tag could be changed to _\#ffffff_ as follows:
body {
font-size: 62.5%;
@ -26,13 +25,13 @@ Any custom changes that you make to the administration interface should be backe
# Modifying the Icecast interface
If you have installed Icecast, in the directory */etc/icecast2/web/* you will find several XSLT and other files which are used to generate the Icecast web interface. If you are familiar with HTML you should be able to modify these pages, as they are well commented. You do have to be careful with syntax, because something as simple as a missing bracket can cause the Icecast web interface to break down.
If you have installed Icecast, in the directory _/etc/icecast2/web/_ you will find several XSLT and other files which are used to generate the Icecast web interface. If you are familiar with HTML you should be able to modify these pages, as they are well commented. You do have to be careful with syntax, because something as simple as a missing bracket can cause the Icecast web interface to break down.
For example, you could change the *status.xsl* page:
For example, you could change the _status.xsl_ page:
sudo nano /etc/icecast2/web/status.xsl
Modifying the *status.xsl* page is a good place to start, because this is the default page that site visitors see when they browse port 8000 on your Icecast server. The most obvious change to make in the XSLT pages is the content of the *&lt;title&gt;* and *&lt;h2&gt;* tags, to announce the name of your station. You can also modify the *style.css* file in this directory to change colour and layout options.
Modifying the _status.xsl_ page is a good place to start, because this is the default page that site visitors see when they browse port 8000 on your Icecast server. The most obvious change to make in the XSLT pages is the content of the _&lt;title&gt;_ and _&lt;h2&gt;_ tags, to announce the name of your station. You can also modify the _style.css_ file in this directory to change colour and layout options.
After saving the file with Ctrl+O, refresh your web browser, and the new look should now be visible.

View File

@ -18,13 +18,13 @@ GNU **gettext** means using a .po file for each language or dialect, a specially
The first of these three lines starts with the hash symbol, and references where this string of text is found in the source code by its file name and line number. If this string is found more than once in the source code, you will see other reference lines here. The second line contains the **msgid**, which is the original version of the string. The third line contains the **msgstr**, which is the translation of that string for the localization that this particular .po file relates to.
If you use the cross-platform program **Poedit** (<http://www.poedit.net/>) to edit the .po file, this formatting of the text is hidden by an easy-to-use GUI. The *poedit* package can be installed on most GNU/Linux distributions using the standard software installer. Versions of Poedit for Mac and Windows are available for free download from the project's homepage.
If you use the cross-platform program **Poedit** (<http://www.poedit.net/>) to edit the .po file, this formatting of the text is hidden by an easy-to-use GUI. The _poedit_ package can be installed on most GNU/Linux distributions using the standard software installer. Versions of Poedit for Mac and Windows are available for free download from the project's homepage.
Before manually translating strings in Poedit from scratch, you should take a look at the online translation services available, such as Lingohub (<https://lingohub.com>) or Google's Translator Toolkit (<http://translate.google.com/toolkit/>), which both support gettext .po files. If using automatic translation, you can then use Poedit to fine-tune the localization and fix any formatting errors.
If you don't already have a GitHub account, you can sign up at <https://github.com/signup/free>. Once you have a GitHub account, you can fork a copy (<https://help.github.com/articles/fork-a-repo>) of the LibreTime project. Work for the next major version of the software is done in the **master** branch of each project, so that's the branch to **checkout** after you have made the initial **git clone**.
In the locale code *de\_CH*, for example, *de* represents the German language and the suffix *\_CH* indicates the dialect spoken in Switzerland. Some languages have a wide variety of dialect localizations, which can be differentiated with a suffix in this way. You should update the header information in the .po file, which includes the language code and a country code, using one of the existing .po files as a guide.
In the locale code _de_CH_, for example, _de_ represents the German language and the suffix _\_CH_ indicates the dialect spoken in Switzerland. Some languages have a wide variety of dialect localizations, which can be differentiated with a suffix in this way. You should update the header information in the .po file, which includes the language code and a country code, using one of the existing .po files as a guide.
After forking the LibreTime git repository, make sure you're in the **master** branch:
@ -32,15 +32,15 @@ After forking the LibreTime git repository, make sure you're in the **master** b
devel
* master
Create a new locale directory (e.g. *airtime\_mvc/locale/de\_CH/LC\_MESSAGES/* for German as spoken in Switzerland):
Create a new locale directory (e.g. _airtime_mvc/locale/de_CH/LC_MESSAGES/_ for German as spoken in Switzerland):
mkdir -p airtime_mvc/locale/de_CH/LC_MESSAGES/
Copy the template *airtime.po* file into the directory you just created:
Copy the template _airtime.po_ file into the directory you just created:
cp airtime_mvc_locale/template/airtime.po airtime_mvc/locale/de_CH/LC_MESSAGES
and update the header information in the new copy of the *airtime.po* file using the **nano** editor:
and update the header information in the new copy of the _airtime.po_ file using the **nano** editor:
nano airtime_mvc/locale/de_CH/LC_MESSAGES/airtime.po

View File

@ -4,12 +4,12 @@ layout: article
category: manager
---
The Listener Stats page on the Analytics menu shows graphs of listener connections to the configured streaming servers for the selected date and time range.  On the right side, a green **Status** indicator shows **OK** if the connection to the streaming server is active.
The Listener Stats page on the Analytics menu shows graphs of listener connections to the configured streaming servers for the selected date and time range. On the right side, a green **Status** indicator shows **OK** if the connection to the streaming server is active.
![](/img/portfolio/stream-stats.jpg)
If the status indicator is red, check that the **Admin User** and **Admin Password** settings are correct under **Additional Options** for the named mount point, such as *libretime\_128*, on the **Streams** page of the **Settings** menu.
If the status indicator is red, check that the **Admin User** and **Admin Password** settings are correct under **Additional Options** for the named mount point, such as _libretime_128_, on the **Streams** page of the **Settings** menu.
To choose which particular streams should have statistics displayed, click the check boxes for the individual colour-coded mount points, just below the graph.
By default, statistics for the last 24 hours of streaming are shown. To change this date and time range, click the calendar and clock icons in the lower left corner of the page, then click the magnifying glass icon.
By default, statistics for the last 24 hours of streaming are shown. To change this date and time range, click the calendar and clock icons in the lower left corner of the page, then click the magnifying glass icon.

View File

@ -39,36 +39,36 @@ for remote input connection details.
**Setup**
1. Download and install [BUTT](https://danielnoethen.de/) for your OS.
*Note: be sure you have butt version 0.1.17 or newer installed*
_Note: be sure you have butt version 0.1.17 or newer installed_
2. Open up BUTT
3. Click **settings**
4. Under **Main** > **Server** click **ADD**
* Type LibreTime (or your station) under Name
* Click the radio button next to **IceCast** under Type
* Type your stations URL (webpage address) under **Address**:
* Type **8002** under **Port**:
* Type your DJ login password under **Password**
* Type **/show** under IceCast mountpoint:
* Type your dj login under **IceCast user:**
- Type LibreTime (or your station) under Name
- Click the radio button next to **IceCast** under Type
- Type your stations URL (webpage address) under **Address**:
- Type **8002** under **Port**:
- Type your DJ login password under **Password**
- Type **/show** under IceCast mountpoint:
- Type your dj login under **IceCast user:**
5. Click **ADD**
6. Still in settings click, **Audio** and select your external sound card under
**Audio Device** *Note: if you only have an internal sound card you maybe able
to use it but that is OS specific and outside of this tutorial. We are assuming
you have a mic and mixer or a USB mixer hooked up to or as an external soundcard*
**Audio Device** _Note: if you only have an internal sound card you maybe able
to use it but that is OS specific and outside of this tutorial. We are assuming
you have a mic and mixer or a USB mixer hooked up to or as an external soundcard_
**Show Time**
1. When its almost your show time go to your LibreTime page and look at the time
in the top right when your show starts go to Butt.
in the top right when your show starts go to Butt.
2. Click the white Play button (third button in the middle).
3. If it says connecting… and then stream time with a counter congratulations,
your are connected!
4. Go to the LibreTime page and at the top right under Source Streams the
tab besides Show Source is to the left and Orange if it is and Current
shows Live Show you are connected.
3. If it says connecting... and then stream time with a counter- congratulations,
your are connected!
4. Go to the LibreTime page and at the top right under Source Streams the
tab besides Show Source is to the left and Orange - if it is and Current
shows Live Show you are connected.
5. If it is gray, click on the **Show Source** switch to the right of it and it
will toggle your show on and you will be broadcasting. *Note: whether auto
connect is turned on is a station specific setting so it could work either way*
will toggle your show on and you will be broadcasting. _Note: whether auto
connect is turned on is a station specific setting so it could work either way_
### Recording your show
@ -79,7 +79,7 @@ directory by default.
Everything should now be working and you can broadcast for your entire time
slot. If you choose to stop streaming before it is over click the white square
**Stop** button to disconnect. Then go to the LibreTime page and if the Show
Source didnt automatically disconnect you can click it to the right and it
Source didn't automatically disconnect you can click it to the right and it
should turn gray.
You are now done streaming.

View File

@ -12,7 +12,7 @@ podcast tabs, and a live feed of your station with information on the the curren
## Modifying the LibreTime Radio Page
The background of the mini-site that appears when you visit the server's domain in your web browser can be changed by modifying the page's CSS file, located at */usr/share/airtime/php/airtime_mvc/public/css/radio-page/radio-page.css*.
The background of the mini-site that appears when you visit the server's domain in your web browser can be changed by modifying the page's CSS file, located at _/usr/share/airtime/php/airtime_mvc/public/css/radio-page/radio-page.css_.
```
html {

View File

@ -30,6 +30,7 @@ If you want to delete the image and start again, run `multipass delete ltTEST &&
### Cloud-init options in cloud-init.yaml
You may wish to change the below fields as per your location.
```
timezone: America/New York # change as needed
ntp:
@ -42,4 +43,4 @@ modify the URL on this line:
```
- cd / && git clone https://github.com/LibreTime/libretime.git
```
```

View File

@ -6,7 +6,7 @@ category: interface
> **About Autoloading Playlists**
>
> Libretime will schedule tracks from a selected playlist an hour before a show is
> Libretime will schedule tracks from a selected playlist an hour before a show is
> scheduled to air. This is a great way to automatically schedule weekly shows which are received
> via. podcasts.
@ -40,20 +40,20 @@ Smart blocks are automatically filled with media files from the LibreTime librar
To create a smart block, click the **Smartblocks** button on the left sidebar, and select **New** from the toolbar. Like a playlist, smart blocks can have a title and description, which you can edit. This helps you find relevant smart blocks in searches.
Fill out the smart block's **Name**, **Search Criteria**, and **Limit to** sections. The search criteria can be any one of LibreTime's metadata categories, such as **Title**, **Creator** or **Genre**. The modifier depends on whether the metadata in question contains letters or numbers. For example, **Title** has modifiers including *contains* and *starts with*, whereas the modifiers for **BPM** include *is greater than* and *is in the range*.
Fill out the smart block's **Name**, **Search Criteria**, and **Limit to** sections. The search criteria can be any one of LibreTime's metadata categories, such as **Title**, **Creator** or **Genre**. The modifier depends on whether the metadata in question contains letters or numbers. For example, **Title** has modifiers including _contains_ and _starts with_, whereas the modifiers for **BPM** include _is greater than_ and _is in the range_.
If you have a large number of files which meet the criteria that you specify, you may wish to limit the duration of the smart block using the **Limit to** field, so that it fits within the show you have in mind. Select **hours**, **minutes** or **items** from the drop-down menu, and click the **Generate** button again, if it is a static smart block. Then click the **Save** button.
> **Note:** Smart Blocks by default will not overflow the length of a scheduled show.
> This is to prevent tracks from being cut-off because they exceed the time limit of a show.
> This is to prevent tracks from being cut-off because they exceed the time limit of a show.
> If you want a smartblock to schedule tracks until it is longer than the Time Limit you can check **"Allow last track to exceed time limit"**
> (helpful for avoiding dead air on autoscheduled shows).
![](/img/Smartblock-advanced.png)
You can also set the **smart block type**. A **Static** smart block will save the criteria and generate the block content immediately. This enables you to edit the contents of the block in the **Library** page before adding it to a show. A **Dynamic** smart block will only save the criteria, and the specific content will be generated at the time the block is added to a show. After that, the content of the show can be changed or re-ordered in the **Now Playing** page. 
You can also set the **smart block type**. A **Static** smart block will save the criteria and generate the block content immediately. This enables you to edit the contents of the block in the **Library** page before adding it to a show. A **Dynamic** smart block will only save the criteria, and the specific content will be generated at the time the block is added to a show. After that, the content of the show can be changed or re-ordered in the **Now Playing** page.
Click the **plus button** on the left to add OR criteria, such as **Creator** containing *beck* OR *jimi*. To add AND criteria, such as **Creator** containing *jimi* AND BPM in the range *120* to *130*, click the **plus button** on the right. (The criteria are not case sensitive). Click **Preview** to see the results.
Click the **plus button** on the left to add OR criteria, such as **Creator** containing _beck_ OR _jimi_. To add AND criteria, such as **Creator** containing _jimi_ AND BPM in the range _120_ to _130_, click the **plus button** on the right. (The criteria are not case sensitive). Click **Preview** to see the results.
> If you see the message **0 files meet the criteria**, it might mean that the files in the Library have not been tagged with the correct metadata. See the chapter [Preparing media](/docs/preparing-media) for tips on tagging content.

View File

@ -32,14 +32,13 @@ The **History Templates** page on the History menu enables you to prepare report
![](/img/new-hist-temp.png)
Either of these actions opens a page in which you can name the new template, and add or remove elements from the list on the left. To add a new element from the list on the right, click the plus icon for the item you require. If the element you require is not listed, you can use the **Add New Field** box at the lower end of the right side column. Select *string*, *boolean*, *integer*, or *float*, depending on the type of data that you wish to log, and then click the **+ Add** button.
Either of these actions opens a page in which you can name the new template, and add or remove elements from the list on the left. To add a new element from the list on the right, click the plus icon for the item you require. If the element you require is not listed, you can use the **Add New Field** box at the lower end of the right side column. Select _string_, _boolean_, _integer_, or _float_, depending on the type of data that you wish to log, and then click the **+ Add** button.
When the template is in the format you require, click the **Save** button, and **Set Default Template** if you wish. The new template will now be listed on the History Templates page. If you have set a new default template, any changes will be visible on the tabs of the Playout History page.
## Exporting the schedule {#exporting}
LibreTime has a feature which enables your station's show and schedule information to be displayed on remote websites. This feature is included in LibreTime because you would not usually invite the general public to access your LibreTime server directly. If you had very large numbers of people requesting data from the LibreTime server at once, the burst of network traffic might overload the server, potentially disrupting your broadcasts. If carried out maliciously, this network overload is known as a *denial of service attack*.
LibreTime has a feature which enables your station's show and schedule information to be displayed on remote websites. This feature is included in LibreTime because you would not usually invite the general public to access your LibreTime server directly. If you had very large numbers of people requesting data from the LibreTime server at once, the burst of network traffic might overload the server, potentially disrupting your broadcasts. If carried out maliciously, this network overload is known as a _denial of service attack_.
Instead, your public-facing web server can retrieve the schedule information from the LibreTime API. It can be presented using Javascript widgets and styled with CSS, in any format that you require.
@ -167,17 +166,17 @@ In this case, the metadata returned would be in a different format from the abov
"sunday":[],
"AIRTIME_API_VERSION":"1.1"})
If you see the message *You are not allowed to access this resource* when attempting to display schedule information in your web browser, log in to the LibreTime administration interface, click *System* in the main menu, then *Preferences*. Set **Allow Remote Websites To Access "Schedule" Info?** to **Enabled**, click the **Save** button, then refresh the browser window opened on the schedule export URL. If you do not wish to make schedule information available to the public, set this option to **Disabled** instead.
If you see the message _You are not allowed to access this resource_ when attempting to display schedule information in your web browser, log in to the LibreTime administration interface, click _System_ in the main menu, then _Preferences_. Set **Allow Remote Websites To Access "Schedule" Info?** to **Enabled**, click the **Save** button, then refresh the browser window opened on the schedule export URL. If you do not wish to make schedule information available to the public, set this option to **Disabled** instead.
### Caching schedule information
If the LibreTime server is behind a firewall, or you want to protect the LibreTime server from large numbers of schedule requests, you may wish to cache the schedule information on a public-facing or intermediate server. You can then create a firewall rule that only allows the schedule server to connect to the LibreTime server, in addition to any remote users of the LibreTime web interface.
Your system administrator can set up schedule caching on a standard Apache and PHP enabled web server with the *curl* program installed, using the following steps:
Your system administrator can set up schedule caching on a standard Apache and PHP enabled web server with the _curl_ program installed, using the following steps:
1. Create a shell script on the schedule server (schedule.example.com) that polls the remote LibreTime server (libretime.example.com), and writes the metadata returned into a pair of local temporary files:
sudo nano /usr/local/bin/libretime-schedule.sh
sudo nano /usr/local/bin/libretime-schedule.sh
The content of this file should be like the following script, replacing libretime.example.com with the name of your LibreTime server:
@ -189,27 +188,27 @@ The content of this file should be like the following script, replacing libretim
2. Make the script executable:
sudo chmod +x /usr/local/bin/libretime-schedule.sh
sudo chmod +x /usr/local/bin/libretime-schedule.sh
3. Create an Apache VirtualHost configuration for the schedule server:
sudo nano /etc/apache2/sites-available/schedule
sudo nano /etc/apache2/sites-available/schedule
containing a definition like the following, replacing *schedule.example.com* with the name of your schedule server:
containing a definition like the following, replacing _schedule.example.com_ with the name of your schedule server:
<VirtualHost *:80>
ServerName schedule.example.com
DocumentRoot /var/www/schedule/
</VirtualHost>
4. In the schedule server's DocumentRoot folder, create the folders *api/live-info/* and *api/week-info/*
4. In the schedule server's DocumentRoot folder, create the folders _api/live-info/_ and _api/week-info/_
sudo mkdir -p /var/www/schedule/api/live-info/
sudo mkdir -p /var/www/schedule/api/week-info/
sudo mkdir -p /var/www/schedule/api/live-info/
sudo mkdir -p /var/www/schedule/api/week-info/
5. Create an index.php file in the *api/live-info/* folder:
5. Create an index.php file in the _api/live-info/_ folder:
sudo nano /var/www/schedule/api/live-info/index.php
sudo nano /var/www/schedule/api/live-info/index.php
containing the following code:
@ -226,9 +225,9 @@ containing the following code:
echo $content;
?>
6. Create an index.php file in the *api/week-info/* folder:
6. Create an index.php file in the _api/week-info/_ folder:
sudo nano /var/www/schedule/api/week-info/index.php
sudo nano /var/www/schedule/api/week-info/index.php
containing the following code:
@ -247,12 +246,12 @@ containing the following code:
7. Enable the new configuration and reload the Apache web server:
sudo a2ensite schedule
sudo /etc/init.d/apache2 reload
sudo a2ensite schedule
sudo /etc/init.d/apache2 reload
8. Create a cron job to run the shell script each minute:
sudo nano /etc/cron.d/libretime-schedule
sudo nano /etc/cron.d/libretime-schedule
containing the line:

View File

@ -6,7 +6,7 @@ category: interface
The Podcasts page allows you add subscriptions to podcasts which are often used to syndicated audio files using a URL called a RSS feed. This allows your LibreTime instance to automatically download new shows from the web.
In order to add a podcast you need to get the RSS feed. All podcasts available on iTunes have a RSS feed but it is sometimes hidden. See this issue on our github page [#510](https://github.com/LibreTime/libretime/issues/510) for more information. RSS feeds that do not end in *.xml* may be accepted by LibreTime but might fail to download episodes; in that case, download the episode using a podcast client such as [gpodder](https://gpodder.github.io/) and then manually upload and schedule the episode. Podcast feeds coming from Anchor.fm have been known to have this issue.
In order to add a podcast you need to get the RSS feed. All podcasts available on iTunes have a RSS feed but it is sometimes hidden. See this issue on our github page [#510](https://github.com/LibreTime/libretime/issues/510) for more information. RSS feeds that do not end in _.xml_ may be accepted by LibreTime but might fail to download episodes; in that case, download the episode using a podcast client such as [gpodder](https://gpodder.github.io/) and then manually upload and schedule the episode. Podcast feeds coming from Anchor.fm have been known to have this issue.
The podcast interfaces provides you with the ability to generate [Smartblocks](/docs/playlists) that can be used in conjunction with autoloading playlists to schedule the newest episode of a podcast without human intervention.
@ -21,7 +21,7 @@ The podcast interfaces provides you with the ability to generate [Smartblocks](/
The podcasts dashboard is similar to the tracks view, allowing you to add, edit, and remove
podcasts by the toolbar, in addition to sorting by columns.
To add a podcast, click on the **+ Add** button on the toolbar and provide the podcast's RSS feed, which usually ends in *.xml*.
To add a podcast, click on the **+ Add** button on the toolbar and provide the podcast's RSS feed, which usually ends in _.xml_.
Once the podcast's feed is recognized, the editor pane opens for the podcast.
### Editor
@ -33,4 +33,4 @@ A search box is available to search for episodes within the feed.
- To import an episode directly into LibreTime, double-click on an episode or select it and click **+ Import**. The podcast will appear under tracks with the Podcast Name as the Album.
- To delete an episode from LibreTime, select the episode and click on the red trash can on the toolbar.
- If you would like LibreTime to automatically download the latest episodes of a podcast, make sure *Download latest episodes* is checked. This can be used in conjunction with Smartblocks and Playlists to automate downloading and scheduling shows that are received via podcast feed.
- If you would like LibreTime to automatically download the latest episodes of a podcast, make sure _Download latest episodes_ is checked. This can be used in conjunction with Smartblocks and Playlists to automate downloading and scheduling shows that are received via podcast feed.

View File

@ -10,20 +10,20 @@ Before uploading media to an LibreTime server, there are a number of factors whi
LibreTime automatically imports any metadata that is in the files' ID3 tags. If these tags are incorrect or are missing information, you will have to either edit the metadata manually, or suffer the consequences. For example, if the files have creator or genre metadata missing, it will be impossible to search for, create playlists or generate smart blocks according to these criteria until you add it.
There are a number of programs available which can be used to correct mistakes or incomplete information in ID3 tags. You can use a music library manager (like Apple Music, Rhythmbox, or Windows Media Player) to edit ID3 tags as well, but you may be required to import the files into your library, which may not always be convenient.
There are a number of programs available which can be used to correct mistakes or incomplete information in ID3 tags. You can use a music library manager (like Apple Music, Rhythmbox, or Windows Media Player) to edit ID3 tags as well, but you may be required to import the files into your library, which may not always be convenient.
- [TagScanner](https://www.xdlab.ru/en/) (Windows)
- [Mp3tag](https://www.mp3tag.de/en/index.html) (Windows)
- [MusicBrainz Picard](https://picard.musicbrainz.org/) (Mac, Windows, Linux)
- [Ex Falso](http://code.google.com/p/quodlibet/) (Linux)
The *Tags From Path* feature of Ex Falso is a particularly useful time saver if you have a large archive of untagged files. Sometimes there is useful creator or title information in the file name or directory path structure, which can be converted into an ID3 tag automatically.
The _Tags From Path_ feature of Ex Falso is a particularly useful time saver if you have a large archive of untagged files. Sometimes there is useful creator or title information in the file name or directory path structure, which can be converted into an ID3 tag automatically.
![](/img/Screenshot175-Ex_Falso.png)
## Metadata in legacy character sets
LibreTime expects file tag metadata to be stored in the international *UTF-8* character set. Programs such as **Ex Falso** (described above) encode metadata in UTF-8 by default. If you have an archive of files encoded with metadata in a legacy character set, such as the Cyrillic encoding *Windows-1251*, you should convert these files before import.
LibreTime expects file tag metadata to be stored in the international _UTF-8_ character set. Programs such as **Ex Falso** (described above) encode metadata in UTF-8 by default. If you have an archive of files encoded with metadata in a legacy character set, such as the Cyrillic encoding _Windows-1251_, you should convert these files before import.
The program **mid3iconv** (part of the **python-mutagen** package in Debian and Ubuntu) can be used to batch convert the metadata character set of files on the command line. You can install **python-mutagen** with the command:
@ -41,27 +41,27 @@ To actually convert all of the tags and strip any legacy ID3v1 tag present from
The name of the original character set follows the **-e** option. Other legacy character sets that mid3iconv can convert to UTF-8 include:
KOI8-R: Russian
KOI8-U: Ukrainian
KOI8-R: Russian
KOI8-U: Ukrainian
GBK: Traditional Chinese
GB2312: Simplified Chinese
GBK: Traditional Chinese
GB2312: Simplified Chinese
EUC-KR: Korean
EUC-JP: Japanese
EUC-KR: Korean
EUC-JP: Japanese
CP1253: Greek
CP1254: Turkish
CP1255: Hebrew
CP1256: Arabic
CP1253: Greek
CP1254: Turkish
CP1255: Hebrew
CP1256: Arabic
## Audio loudness
On file ingest, LibreTime analyzes each Ogg Vorbis, MP3, AAC or FLAC file's loudness, and stores a *ReplayGain* value for that file in its database. At playout time, the ReplayGain value is provided to Liquidsoap so that gain can be automatically adjusted to provide an average output of -14 dBFS loudness (14 decibels below full scale). See <http://www.replaygain.org/> for more details of ReplayGain.
On file ingest, LibreTime analyzes each Ogg Vorbis, MP3, AAC or FLAC file's loudness, and stores a _ReplayGain_ value for that file in its database. At playout time, the ReplayGain value is provided to Liquidsoap so that gain can be automatically adjusted to provide an average output of -14 dBFS loudness (14 decibels below full scale). See <http://www.replaygain.org/> for more details of ReplayGain.
Because of this automatic gain adjustment, any files with average loudness higher than -14 dBFS will not sound louder than quieter files at playout time, but the lower crest factor in the louder files (their relatively low peak-to-average ratio) may be apparent in the output, making those files sound less dynamic. This may be an issue for contemporary popular music, which can average at -9 dBFS or louder before ReplayGain adjustment. (See <http://www.soundonsound.com/sos/sep11/articles/loudness.htm> for a detailed analysis of the problem).
Your station's producers should therefore aim for 14dB between peak and average loudness to maintain the crest factor of their prepared material (also known as *DR14* on some dynamic range meters, such as the command-line DR14 T.meter available from <http://sourceforge.net/projects/dr14tmeter/>). If the producers are working to a different loudness standard, the ReplayGain modifier in LibreTime's Stream Settings page can be adjusted to suit their material.
Your station's producers should therefore aim for 14dB between peak and average loudness to maintain the crest factor of their prepared material (also known as _DR14_ on some dynamic range meters, such as the command-line DR14 T.meter available from <http://sourceforge.net/projects/dr14tmeter/>). If the producers are working to a different loudness standard, the ReplayGain modifier in LibreTime's Stream Settings page can be adjusted to suit their material.
Large transient peaks in otherwise quiet files should be avoided, to guard against the need for peak limiting when ReplayGain is applied to those quieter files.
@ -72,7 +72,7 @@ Here is an example of a very quiet file where the use of ReplayGain would make t
$ vorbisgain -d Peter_Lawson-Three_Gymn.ogg
Analyzing files...
   Gain | Peak | Scale | New Peak | Track
Gain | Peak | Scale | New Peak | Track
----------+------+-------+----------+------
+17.39 dB | 4536 | 7.40 | 33585 | Peter_Lawson-Three_Gymn.ogg
@ -81,11 +81,11 @@ And here is an example of a very loud file, with lower crest factor, where the o
$ vorbisgain -d Snoop_Dogg-Doggfather.ogg
Analyzing files...
   Gain   | Peak  | Scale | New Peak | Track
Gain | Peak | Scale | New Peak | Track
----------+-------+-------+----------+------
 -7.86 dB | 36592 |  0.40 |    14804 | Snoop_Dogg-Doggfather.ogg
-7.86 dB | 36592 | 0.40 | 14804 | Snoop_Dogg-Doggfather.ogg
In the output from vorbisgain, *Peak* is the maximum sample value of the file before any ReplayGain has been applied, where a value of 32,767 represents full scale when decoding to signed 16 bit samples. Note that lossy compressed files can have peaks greater than full scale, due to encoding artifacts. The *New Peak* value for the Snoop Dogg file may be relatively low due to the hard limiting used in the mastering of that piece of music.
In the output from vorbisgain, _Peak_ is the maximum sample value of the file before any ReplayGain has been applied, where a value of 32,767 represents full scale when decoding to signed 16 bit samples. Note that lossy compressed files can have peaks greater than full scale, due to encoding artifacts. The _New Peak_ value for the Snoop Dogg file may be relatively low due to the hard limiting used in the mastering of that piece of music.
## Silence in media files

View File

@ -4,7 +4,6 @@ layout: article
category: install
---
In some deployments, the LibreTime server is deployed behind a reverse proxy,
for example in containerization use-cases such as Docker and LXC. LibreTime
makes extensive use of its API for some site functionality, which causes

View File

@ -14,7 +14,7 @@ SoundExchange in the USA, and similar organizations in other territories. These
organizations are usually membership societies or government-sanctioned national
authorities which are intended to collect money from broadcasters to compensate
copyright holders. The royalty collection societies require payment before you
can stream just about any music released commercially to the general public
can stream just about any music released commercially to the general public -
whether you make any money out of streaming, or not. It's not so much the
percentage of revenue demanded, but that there are usually annual minimum fees
to pay, which can hurt small stations disproportionately.

View File

@ -33,29 +33,29 @@ check the **Repeats?** box and fill out the repeat information. A description of
are in the table below. Finially, click on the grey **+ Add this show** button at the top
of the pane to add your show to the calendar.
| Field | Description |
|-------|-------|
| _What_ | |
| Name (Required) | The name of your show |
| URL | The URL of your show. Not used on the public page. |
| Genre | The genre of your show. Not used on the public page. |
| Description | Description of your show. Not used on the public page. |
| _When_ | |
| Start Time (Required) | The time and date the show starts. Note that the time element is in 24 hour time. If the **Now** option is selected, the show will be created to immediately start. |
| End Time (Required) | The time and date the show ends. Defaults to a time one hour after the start time, which can be seen in the **Duration** field, which is uneditable. |
| Repeats? | If checked, allows for options to schedule a repeated show. Shows can repeat weekly up to monthly in increments of one week and can be scheduled on multiple days of the same week. An end date can be set, otherwise the show can be deleted by clicking on its entry in the calendar and clicking Delete > Future Occurances. If **Linked ?** is checked, the playlist scheduled for the next show will also play for all future shows. |
| _Autoloading Playlist_ | |
| Add Autoloading Playlist? | If checked, allows for the following options |
| Select Playlist | Select the playlist the show will autofill from (shows autofill exactly one hour before air). If you wish to use a smartblock you must add it to a playlist and then select that playlist. This can be used to auto-schedule new podcast episodes to air. |
| Repeat Playlist Until Show Is Full | If checked, the playlist will be added to the show multiple times until the slot is full. Useful for applying a one-hour music playlist made up of smartblocks to a two-hour show. |
| _Live Stream Input_ | |
| Use LibreTime/Custom Authentication | |
| Show Source | |
| _Who_ | |
| Search Users, DJs | Program Managers and Admins may assign DJs to a show, giving DJs access to schedule tracks for said show. DJs cannot create shows on their own. |
| _Style_ | |
| Background/Text Color | Set the color of the background and text of entries on the calendar. If not set, LibreTime will select contrasting colors for easy readability. |
| Show Logo | If desired, you can upload a show logo here. The logo does not appear on the public page. |
| Field | Description |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| _What_ | |
| Name (Required) | The name of your show |
| URL | The URL of your show. Not used on the public page. |
| Genre | The genre of your show. Not used on the public page. |
| Description | Description of your show. Not used on the public page. |
| _When_ | |
| Start Time (Required) | The time and date the show starts. Note that the time element is in 24 hour time. If the **Now** option is selected, the show will be created to immediately start. |
| End Time (Required) | The time and date the show ends. Defaults to a time one hour after the start time, which can be seen in the **Duration** field, which is uneditable. |
| Repeats? | If checked, allows for options to schedule a repeated show. Shows can repeat weekly up to monthly in increments of one week and can be scheduled on multiple days of the same week. An end date can be set, otherwise the show can be deleted by clicking on its entry in the calendar and clicking Delete > Future Occurrences. If **Linked ?** is checked, the playlist scheduled for the next show will also play for all future shows. |
| _Autoloading Playlist_ | |
| Add Autoloading Playlist? | If checked, allows for the following options |
| Select Playlist | Select the playlist the show will autofill from (shows autofill exactly one hour before air). If you wish to use a smartblock you must add it to a playlist and then select that playlist. This can be used to auto-schedule new podcast episodes to air. |
| Repeat Playlist Until Show Is Full | If checked, the playlist will be added to the show multiple times until the slot is full. Useful for applying a one-hour music playlist made up of smartblocks to a two-hour show. |
| _Live Stream Input_ | |
| Use LibreTime/Custom Authentication | |
| Show Source | |
| _Who_ | |
| Search Users, DJs | Program Managers and Admins may assign DJs to a show, giving DJs access to schedule tracks for said show. DJs cannot create shows on their own. |
| _Style_ | |
| Background/Text Color | Set the color of the background and text of entries on the calendar. If not set, LibreTime will select contrasting colors for easy readability. |
| Show Logo | If desired, you can upload a show logo here. The logo does not appear on the public page. |
Once your show is created, click on it to open its context menu. Select **Schedule Tracks** to open the track scheduler.
@ -80,4 +80,3 @@ When media is playing, the **On Air** indicator at the top will turn red.
You can listen to your stream by going to `yourserverIP:8000` or by clicking the **Listen** button under the On Air
indicator.

View File

@ -7,7 +7,7 @@ category: install
Accurate time keeping on your server is vital for LibreTime performance. You can confirm that the date and time of your server are set correctly with the `date` command.
The server should respond with the date, time, time zone and year in a format similar to the following example:
Tue Jul 2 15:08:57 BST 2013
Tue Jul 2 15:08:57 BST 2013
If the time on your server is wrong, it is strongly recommended that you take LibreTime off-air until the problem is fixed.
@ -21,7 +21,7 @@ Optionally, open the **ntp** configuration file in the **nano** editor to add fu
sudo nano /etc/ntp.conf
On Ubuntu GNU/Linux, the default time server is *ntp.ubuntu.com*, but there are many other time servers available on the public Internet, including the group of servers listed at <http://www.pool.ntp.org/> for each country. Using a variety of NTP servers located closely to your LibreTime server should produce the most accurate results. For example, for a server in the United Kingdom you could use the following list:
On Ubuntu GNU/Linux, the default time server is _ntp.ubuntu.com_, but there are many other time servers available on the public Internet, including the group of servers listed at <http://www.pool.ntp.org/> for each country. Using a variety of NTP servers located closely to your LibreTime server should produce the most accurate results. For example, for a server in the United Kingdom you could use the following list:
# You do need to talk to an NTP server or two (or three).
server ntp.ubuntu.com
@ -30,29 +30,29 @@ On Ubuntu GNU/Linux, the default time server is *ntp.ubuntu.com*, but there are
server 2.uk.pool.ntp.org
server 3.uk.pool.ntp.org
Enter the server names you require, press **Ctrl+O** to write out the */etc/ntp.conf* file, then **Ctrl+X** to exit **nano**. Restart the **ntp** service with:
Enter the server names you require, press **Ctrl+O** to write out the _/etc/ntp.conf_ file, then **Ctrl+X** to exit **nano**. Restart the **ntp** service with:
sudo invoke-rc.d ntp restart
The server should respond:
* Stopping NTP server ntpd                                 [ OK ]
* Starting NTP server ntpd                                 [ OK ]
* Stopping NTP server ntpd [ OK ]
* Starting NTP server ntpd [ OK ]
Then use the **ntpq -p** command to confirm that **ntp** is working. This command should produce output similar to the following:
ntpq -p
remote      refid    st t when poll reach delay offset jitter
remote refid st t when poll reach delay offset jitter
==================================================================
europium. 193.79.237.14  2 u   28   64    3 39.571  12.600  3.590
norb.v4.c 46.227.200.72  3 u   28   64    3 47.856  -6.908 10.028
82.113.15 193.62.22.82   2 u   29   64    3 11.458  -0.513  2.629
ntppub.le 158.43.192.66  2 u   91   64    2 122.781  44.864  0.001
dns0.rmpl 195.66.241.3   2 u   27   64    3  22.171   1.464  4.242
europium. 193.79.237.14 2 u 28 64 3 39.571 12.600 3.590
norb.v4.c 46.227.200.72 3 u 28 64 3 47.856 -6.908 10.028
82.113.15 193.62.22.82 2 u 29 64 3 11.458 -0.513 2.629
ntppub.le 158.43.192.66 2 u 91 64 2 122.781 44.864 0.001
dns0.rmpl 195.66.241.3 2 u 27 64 3 22.171 1.464 4.242
### Adjusting the server time zone
The data centre which hosts your LibreTime server could be located anywhere in the world. Some servers are set to *Coordinated Universal Time* or UTC (similar to *Greenwich Mean Time* or GMT), regardless of their location. LibreTime uses UTC time in its database for scheduling purposes, independent of the server time zone.
The data centre which hosts your LibreTime server could be located anywhere in the world. Some servers are set to _Coordinated Universal Time_ or UTC (similar to _Greenwich Mean Time_ or GMT), regardless of their location. LibreTime uses UTC time in its database for scheduling purposes, independent of the server time zone.
If the server time zone is not appropriate for integration with your station's other systems, on a Debian or Ubuntu server you can reconfigure the **tzdata** (time zone data) package with the command:

View File

@ -4,7 +4,6 @@ title: Settings
category: admin
---
## General Settings
![](/img/station-info-settings.png)
@ -19,7 +18,7 @@ Description** and **Station Logo** here.
The **Default Interface Language** drop-down menu sets the default localization
for your LibreTime instance, and the **Station Timezone** drop-down menu can be
used to display local time at your station. LibreTime stores show times
internally in UTC format (similar to *Greenwich Mean Time*), but displays local
internally in UTC format (similar to _Greenwich Mean Time_), but displays local
time for the convenience of your station staff. You can also set the day of the
week that you wish to start your station's weekly schedule on, which defaults
to Sunday.
@ -29,7 +28,7 @@ The **Track Type Default** enables you to select a track type default for upload
Initially, the **Default Fade In** and **Default Fade Out** times for automated
fades are set to half a second, and the **Default Crossfade Duration** is set to
zero seconds. Custom fade and crossfade times can be set for adjacent items in a
playlist or static smart block. See the chapter *Library* for details.
playlist or static smart block. See the chapter _Library_ for details.
The **Intro Autoloading Playlist** enables you to select a playlist that will be
scheduled at the beginning of every show that has enabled an autoloading
@ -60,8 +59,8 @@ refactors. You can switch back at any time.
You can enable live, read-only access to the LibreTime schedule calendar for
your station's public website with the **Public LibreTime API** option, if you
wish. (There is more about this feature in the
[*Exporting the schedule*](/docs/playout-history) chapter, in the
*Advanced Configuration* section of this book).
[_Exporting the schedule_](/docs/playout-history) chapter, in the
_Advanced Configuration_ section of this book).
The **Allowed CORS URLs** is intended to deal with situations where you want a
remote site with a different domain to access the API. This is relevant when
@ -90,7 +89,7 @@ Individual LibreTime users can choose another interface localization when they
log in, or set personal preferences for localization and time zone by clicking
their username on the right side of the menu bar.
----
---
## Track Types {#types}
@ -105,7 +104,7 @@ their username on the right side of the menu bar.
1. On the "Visibility" drop down menu, choose to enable or disable the track type. By default, it is enabled. If disabled, it won't be shown across Libretime or in the API for developers.
1. Click **Save**.
----
---
## Stream Settings
@ -115,11 +114,11 @@ their username on the right side of the menu bar.
You can configure direct Icecast and SHOUTcast streams and sound card output by clicking **Streams** on the **System** menu.
At the top left of the **Stream Settings** page are global settings including **Hardware Audio Output**, which enables playout from the default sound card on the server, if one is fitted. The default **Output Type** of *ALSA* on the drop-down menu will be suitable for most servers with a sound card. If not, you have the option to choose from other Liquidsoap interfaces available, such as *OSS* or *PortAudio*.
At the top left of the **Stream Settings** page are global settings including **Hardware Audio Output**, which enables playout from the default sound card on the server, if one is fitted. The default **Output Type** of _ALSA_ on the drop-down menu will be suitable for most servers with a sound card. If not, you have the option to choose from other Liquidsoap interfaces available, such as _OSS_ or _PortAudio_.
The second checkbox under Global Settings enables the sending of **Icecast Vorbis Metadata** with direct streams. This setting is optional, because some media players have a bug which makes them disconnect from Ogg Vorbis streams when an Icecast server notifies the player that a new track is starting.
The **Stream Label** radio button allows you to set the metadata that will be sent with direct streams; *Artist* and *Title*, *Show*, *Artist* and *Title*, or *Station name* and *Show name*.
The **Stream Label** radio button allows you to set the metadata that will be sent with direct streams; _Artist_ and _Title_, _Show_, _Artist_ and _Title_, or _Station name_ and _Show name_.
The **Off Air Metadata** field configures the text that will be sent to any configured streaming servers, and from there on to media players, when Airtime is not streaming any output.
@ -137,7 +136,7 @@ Airtime supports two types of live input stream; the **Show Source**, which enab
The **Auto Switch Off** and **Auto Switch On** checkboxes enable playout to be switched automatically to the highest priority source whenever an authenticated input source disconnects from or connects to Airtime, respectively. The field **Switch Transition Fade** sets the length of the audio fade as scheduled playout is switched to a remote input source, and back.
Each type of input stream requires a username and password before the remote broadcaster can connect to Airtime. The **Master Username** and **Master Password** can be set in the Input Stream Settings box, while the authentication for individual Show Sources is set up in Airtime's schedule calendar. See the *Calendar* chapter for details.
Each type of input stream requires a username and password before the remote broadcaster can connect to Airtime. The **Master Username** and **Master Password** can be set in the Input Stream Settings box, while the authentication for individual Show Sources is set up in Airtime's schedule calendar. See the _Calendar_ chapter for details.
Input streams must have a **Port** for the remote broadcaster to connect to, which should be a number in the range from 1024 to 49151. If you have the Icecast or SHOUTcast streaming server running on the same machine as Airtime, you should avoid using port 8000 or 8001 for either type of Airtime input stream. This is because both Icecast and SHOUTcast use port 8000, and SHOUTcast also uses port 8001. If the usernames and passwords were similar, remote broadcasters might accidentally connect to the streaming server directly, bypassing Airtime.
@ -155,7 +154,7 @@ If you have checked the **Auto Switch On** box in the Stream Settings page, the
![](/img/libretime-show-source-stream.png)
If you have the **Auto Switch Off** box checked LibreTime will resume scheduled playback whenever a stream disconnects. Otherwise you will need to slide to disable a source after a DJ stops streaming.
If you have the **Auto Switch Off** box checked LibreTime will resume scheduled playback whenever a stream disconnects. Otherwise you will need to slide to disable a source after a DJ stops streaming.
You can also force disconnection of a live remote source, for example when the remote input source has crashed and is no longer sending audio data, click the **X** icon to the left of the source name.
@ -167,11 +166,10 @@ On the right side of the page, you can configure up to three independent output
To configure another stream, click the bar with the stream number to expand its box, and make sure **Enabled** is checked. Enter at least the streaming **Server** IP address or domain name, and **Port** details. The default port for Icecast and SHOUTcast servers is 8000.
Click **Additional Options** to expand a box in which you can enter the usernames, passwords and metadata to send to the streaming server. The default **Username** for Icecast servers is *source*, and if this the name in use on your streaming server, you can leave this field empty. The **Admin User** and **Admin Password** settings are optional, and are used to query the streaming server for audience numbers by the **Listener Stats** page on the **System** menu.
Click **Additional Options** to expand a box in which you can enter the usernames, passwords and metadata to send to the streaming server. The default **Username** for Icecast servers is _source_, and if this the name in use on your streaming server, you can leave this field empty. The **Admin User** and **Admin Password** settings are optional, and are used to query the streaming server for audience numbers by the **Listener Stats** page on the **System** menu.
You can also set the specific **Mount Point** that listeners will connect to here. Then click one of the **Save** buttons in the upper or lower right corner of the page to update the Airtime server's settings.
Airtime supports output to Icecast in Ogg Vorbis, Ogg Opus, MP3 and AAC formats. When selecting a SHOUTcast server from the **Service Type** drop-down menu, you are restricted to using MP3 or AAC formats only, so the choice of Ogg Vorbis and Opus formats is greyed out in the **Stream Type** drop-down menu. The SHOUTcast username for stream sources is fixed, so you do not need to enter this value under **Additional Options**, but you will usually have to enter a password.
Any connection problems between Liquidsoap and Icecast or SHOUTcast are shown on the Stream Settings page. For example, if you enter the wrong password, you will see an *Authentication Required* error message. To fix this, enter the correct password in the **Additional Options** box, and click the **Save** button. If the streaming server is down for any reason, or you have entered an incorrect **Server** name or **Port** number, you will see the message *Can not connect to the streaming server*.
Any connection problems between Liquidsoap and Icecast or SHOUTcast are shown on the Stream Settings page. For example, if you enter the wrong password, you will see an _Authentication Required_ error message. To fix this, enter the correct password in the **Additional Options** box, and click the **Save** button. If the streaming server is down for any reason, or you have entered an incorrect **Server** name or **Port** number, you will see the message _Can not connect to the streaming server_.

View File

@ -10,6 +10,7 @@ To increase the security of your server, you can enable encrypted access to the
One of the fastest, easiest, and cheapest ways to get an SSL certificate is through [Certbot](https://certbot.eff.org/), as created by the
Electronic Frontier Foundation. There are some requirements for this process:
- you have an HTTP website (already installed and configured by default by the LibreTime installer) and
- this website is open to the public internet (likely via. port forwarding if your computer is behind a firewall) and
- the server is accessible to the public via. port 80
@ -20,7 +21,7 @@ These instructions come from Certbot's website and assume that you are using an
running on Ubuntu 18.04 LTS (the Apache web server is installed with LibreTime by default).
Instructions for other Debian-based OSes are similar, but check with Certbot for clarification.
Note: all instructions require you to have sudo priveledges
Note: all instructions require you to have sudo privileges
First, add Certbot's PPA using:
@ -45,11 +46,11 @@ Head to your server's IP address to check to see that the installation worked.
### Deploying a self-signed certificate
The Debian/Ubuntu package *ssl-cert* creates a *snakeoil* certificate and key based on your server's hostname. This gratis certificate and key pair created under the */etc/ssl/certs*/ and */etc/ssl/private/* directories will not be recognised by users' browsers without manual intervention. You can install the *ssl-cert* package with the command:
The Debian/Ubuntu package _ssl-cert_ creates a _snakeoil_ certificate and key based on your server's hostname. This gratis certificate and key pair created under the _/etc/ssl/certs_/ and _/etc/ssl/private/_ directories will not be recognised by users' browsers without manual intervention. You can install the _ssl-cert_ package with the command:
sudo apt-get install ssl-cert
If the hostname of your server does not match the domain name you intend to use with the LibreTime virtual host, the user's browser will present an additional security warning. You can set the domain name of the certificate by editing the file */usr/share/ssl-cert/ssleay.cnf* to replace the *@HostName@* variable:
If the hostname of your server does not match the domain name you intend to use with the LibreTime virtual host, the user's browser will present an additional security warning. You can set the domain name of the certificate by editing the file _/usr/share/ssl-cert/ssleay.cnf_ to replace the _@HostName@_ variable:
commonName = @HostName@
@ -69,9 +70,9 @@ Next, edit the virtual host configuration for your LibreTime server to include a
sudo nano /etc/apache2/sites-available/airtime-vhost.conf
Using the following configuration for Apache 2.2 as a guide, replace *airtime.example.com* with the name of your server and *admin@example.com* with your email address. The older SSLv2 and SSLv3 protocols and SSL compression should be disabled, as they are generally believed to be insecure. You may wish to create a *ServerAlias* for users to access the administration interface over https:// if required.
Using the following configuration for Apache 2.2 as a guide, replace _airtime.example.com_ with the name of your server and *admin@example.com* with your email address. The older SSLv2 and SSLv3 protocols and SSL compression should be disabled, as they are generally believed to be insecure. You may wish to create a _ServerAlias_ for users to access the administration interface over https:// if required.
On port 80, Apache's *alias* module is used to set a *Redirect permanent* for the login page. Optionally, access could be denied to all sites except *localhost* and any other LibreTime servers on your network, so that unencrypted communication between LibreTime components can continue.
On port 80, Apache's _alias_ module is used to set a _Redirect permanent_ for the login page. Optionally, access could be denied to all sites except _localhost_ and any other LibreTime servers on your network, so that unencrypted communication between LibreTime components can continue.
```
<VirtualHost *:443>
@ -114,7 +115,7 @@ On port 80, Apache's *alias* module is used to set a *Redirect permanent* for th
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
</VirtualHost>
```
Save the file with **Ctrl+O** and exit the **nano** editor with **Ctrl+X**. Then restart Apache with the command:
@ -129,7 +130,7 @@ The first time you access an LibreTime server with a self-signed certificate ove
![](/img/Screenshot547-connection_untrusted.png)
On the next page in Firefox, click the **Get Certificate** button to inspect the details of the self-signed certificate. If all is well, click the **Confirm Security Exception** button. You should now be able to proceed to the https:// login page.  
On the next page in Firefox, click the **Get Certificate** button to inspect the details of the self-signed certificate. If all is well, click the **Confirm Security Exception** button. You should now be able to proceed to the https:// login page.
![](/img/Screenshot548-confirm_exception.png)

View File

@ -14,5 +14,5 @@ If any of the check mark icons in the **Status** column have changed to a red wa
administrator for assistance. (The chapter [Troubleshooting](/docs/troubleshooting) contains some tips). LibreTime will
do its best to restart any failing services, but sometimes manual intervention may be required; for example, in the case of hardware failure.
If you have run out of storage space, a LibreTime user with *admin* privileges could log in and delete media files
If you have run out of storage space, a LibreTime user with _admin_ privileges could log in and delete media files
that are no longer required from the **Library**. Alternatively, you could ask your system administrator to install additional storage capacity.

View File

@ -4,16 +4,16 @@ title: Troubleshooting
category: admin
---
Is something not working for your Libretime installation? Here's a quick guide to help you
Is something not working for your Libretime installation? Here's a quick guide to help you
troubleshoot most issues you'll run into.
## 1. Let's check the basics
Is your server on? (We hate to ask.) Is it connected to the internet? Is it connected to your
broadcast console or mixer if being used for soundcard output? If you're using a cloud host,
broadcast console or mixer if being used for soundcard output? If you're using a cloud host,
does your cloud provider's status page indicate any system outages?
Once you know your physical (or virtual) system is functional, was a show scheduled for the
Once you know your physical (or virtual) system is functional, was a show scheduled for the
current time with tracks or an autoplaylist scheduled?
## 2. Are all services working?
@ -23,8 +23,8 @@ A fully working server should have green checkmarks next to all services.
![](/img/Screenshot521-System_status_240.png)
If one of the services isn't working, text will display with a terminal command to restart the service
or get status information for a particular service. For example (for Ubuntu 18.04), the following
If one of the services isn't working, text will display with a terminal command to restart the service
or get status information for a particular service. For example (for Ubuntu 18.04), the following
commands would restart or check the status of Libretime's Liquidsoap instance, respectively.
```
@ -38,14 +38,14 @@ If the service isn't wanting to restart, look at its status for clues as to why
## 3. Known problems
If you have one of these issues, please try to resolve it with the instructions below before moving on in the
If you have one of these issues, please try to resolve it with the instructions below before moving on in the
troubleshooting checklist.
- **Streaming player on Microsite and Listen player on Dashboard not working?** The problem could be caused by a bug in writing to the database during the setup wizard. This can be fixed by going to **Settings** -> **Stream Settings** and toggling the **Default Streaming** and **Custom/ 3rd Party Streaming** option, accepting the popup dialogues, and clicking **Save** at the top of the settings page.
- **File not importing successfully?** Libretime has been known to work with MP3 and WAV files, encoded using 41,100 Hz. Variable Bit Rate (VBR) files are currently hit or miss with the importer. Please convert your file to an MP3 or WAV at 41,100 Hz. and try uploading again.
- **Podcast hosted by Anchor.fm not importing?** There is no known work-around at this time. Ask your producers to provide their show files manually or check with the show's distributer.
- **Tracks won't publish?** We know the Publish screen is broken and we're working on it. A potential work-around is to use an external podcast host like [Anchor.fm](https://www.anchor.fm) or [Blubrry](https://blubrry.com/).
- **Can't hear any sound coming from your soundcard (for analog audio output)?** If you are using ALSA as your audio driver, use `alsamixer` to see the current volume your system is set to. If stil nothing, go to **Settings** > **Streams** and make sure **Hardware Audio Output** is checked. If you need to play a tone to help you troubleshoot, you can use `speaker-test` (does not come installed with Libretime).
- **Can't hear any sound coming from your soundcard (for analog audio output)?** If you are using ALSA as your audio driver, use `alsamixer` to see the current volume your system is set to. If still nothing, go to **Settings** > **Streams** and make sure **Hardware Audio Output** is checked. If you need to play a tone to help you troubleshoot, you can use `speaker-test` (does not come installed with Libretime).
## 4. Read the docs
@ -53,7 +53,7 @@ Our main documentation listing is [here](/docs) and can be searched [here](/sear
## 5. Reach out to the developers
Libretime is still in active development, meaning bugs and issues are expected to pop up every so often.
See if an issue is still open by looking at our [Issues page](https://github.com/LibreTime/libretime/issues).
If you don't get the help you need, please [open an issue](https://github.com/LibreTime/libretime/issues/new/choose)
Libretime is still in active development, meaning bugs and issues are expected to pop up every so often.
See if an issue is still open by looking at our [Issues page](https://github.com/LibreTime/libretime/issues).
If you don't get the help you need, please [open an issue](https://github.com/LibreTime/libretime/issues/new/choose)
so we can take a look at it.

View File

@ -19,14 +19,13 @@ of dot separated identifiers immediately following the patch version. This pre-r
that the version is unstable in a sense that it might contain incomplete features or not satisfy the
intended compatibility requirements as per semver.
## Upgrading
## Upgrading
> After your LibreTime server has been deployed for a few years, you may need to
upgrade the GNU/Linux distribution that it runs in order to maintain security
update support. If the upgrade does not go smoothly, it may cause significant
downtime, so you should always have a fallback system available during the
upgrade to ensure broadcast continuity.
> upgrade the GNU/Linux distribution that it runs in order to maintain security
> update support. If the upgrade does not go smoothly, it may cause significant
> downtime, so you should always have a fallback system available during the
> upgrade to ensure broadcast continuity.
Before upgrading a production LibreTime server, you should back up both the PostgreSQL
database and the storage server used by LibreTime. This is especially important if you have not already
@ -34,14 +33,14 @@ set up a regular back up routine. This extra back up is a safety measure in case
during the upgrade, for example due to the wrong command being entered when moving files. See
[Backing up the server](/docs/backing-up-the-server) in this manual for details of how to perform these back ups.
The LibreTime [installation script](/install) will detect an existing LibreTime or Airtime deployment and back up any configuration files that it finds. We recommend taking your own manual backups of the configuration yourself nevertheless. The install script also tries to restart the needed services during an upgrade. In any case you should monitor if this happened and also take a quick look at the logs files to be sure everything is still fine. Now might be the time to reboot the system or virtual machine LibreTime is running on since regular reboots are part of a healthy system anyway.
The LibreTime [installation script](/install) will detect an existing LibreTime or Airtime deployment and back up any configuration files that it finds. We recommend taking your own manual backups of the configuration yourself nevertheless. The install script also tries to restart the needed services during an upgrade. In any case you should monitor if this happened and also take a quick look at the logs files to be sure everything is still fine. Now might be the time to reboot the system or virtual machine LibreTime is running on since regular reboots are part of a healthy system anyway.
After the upgrade has completed, you may need to clear your web browser's cache before logging into the new version of the LibreTime administration interface. If the playout engine starts up and detects that a show should be playing at the current time, it will skip to the correct point in the current item and start playing.
After the upgrade has completed, you may need to clear your web browser's cache before logging into the new version of the LibreTime administration interface. If the playout engine starts up and detects that a show should be playing at the current time, it will skip to the correct point in the current item and start playing.
There will be tested ways to switch from a LibreTime pre-release to a packaged version of LibreTime.
Airtime 2.5.x versions support upgrading from version 2.3.0 and above. If you are
running a production server with a version of Airtime prior to 2.3.0, you should
upgrade it to version 2.3.0 before continuing. 
upgrade it to version 2.3.0 before continuing.
> **Note:** Airtime's *linked files* and *watched folders* features currently do not work in Libretime.
> **Note:** Airtime's _linked files_ and _watched folders_ features currently do not work in Libretime.

View File

@ -5,12 +5,12 @@ category: interface
---
> Note: if your Libretime server is accessible from the public Internet (ex. being hosted in a cloud VM)
it is strongly recommended to create a second administrator account with a secure password and then
delete the `admin` account.
> it is strongly recommended to create a second administrator account with a secure password and then
> delete the `admin` account.
## User Account Types
To add further user accounts to the system, one for each of your station staff that need access to Airtime, click the **New User** button with the plus icon. Enter a user name, password and contact details, and then select the **User Type** from the drop down menu, which can be *Admin*, *Program Manager*, *DJ*, or *Guest*. The difference between these user types is:
To add further user accounts to the system, one for each of your station staff that need access to Airtime, click the **New User** button with the plus icon. Enter a user name, password and contact details, and then select the **User Type** from the drop down menu, which can be _Admin_, _Program Manager_, _DJ_, or _Guest_. The difference between these user types is:
**Guests**
@ -29,7 +29,7 @@ To add further user accounts to the system, one for each of your station staff t
- Everything DJs can do, plus
- Manage other users' libraries in addition to their own
- Create, edit, and delete color-coded shows on the Calender and assign them to DJs (if needed)
- Create, edit, and delete color-coded shows on the Calendar and assign them to DJs (if needed)
- Shows can be scheduled to repeat, with the option of linking content between the shows (helpful if a DJ livestreams in each week)
- View listener statistics
- Export playout logs for analysis or reporting for music royalties
@ -58,4 +58,3 @@ side of its row in the table. You cannot delete your own user account, and usern
Users can update their own password, and their contact, language and time zone details, by clicking their username on the
right side of the main menu bar, next to the **Logout** link.

View File

@ -91,13 +91,13 @@ directory.
With the above instructions LibreTime is installed on Ubuntu Xenial Xerus. The Vagrant setup
offers the option to choose a different operation system according to you needs.
| OS | Command | Comment |
| ------ | ------------------- | ------- |
| Debian 10 | `vagrant up debian-buster` | Install on Debian Buster. |
| Debian 9 | `vagrant up debian-stretch` | Install on current Debian Stretch. |
| Ubuntu 18.04 | `vagrant up ubuntu-bionic` | Install on current Ubuntu Bionic Beaver. |
| Ubuntu 16.04 | `vagrant up ubuntu-xenial` | Install on Ubuntu Xenial Xerus. |
| CentOS | `vagrant up centos` | CentOS 8 with native systemd support and activated SELinux. |
| OS | Command | Comment |
| ------------ | --------------------------- | ----------------------------------------------------------- |
| Debian 10 | `vagrant up debian-buster` | Install on Debian Buster. |
| Debian 9 | `vagrant up debian-stretch` | Install on current Debian Stretch. |
| Ubuntu 18.04 | `vagrant up ubuntu-bionic` | Install on current Ubuntu Bionic Beaver. |
| Ubuntu 16.04 | `vagrant up ubuntu-xenial` | Install on Ubuntu Xenial Xerus. |
| CentOS | `vagrant up centos` | CentOS 8 with native systemd support and activated SELinux. |
## Troubleshooting

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