From b872c146d7baf53fb73562f4b23f9918555cd4dc Mon Sep 17 00:00:00 2001 From: Kyle Robbertze Date: Mon, 21 Oct 2019 20:40:29 +0200 Subject: [PATCH 01/23] add OpenAPI doc of the current API --- docs/api/openapi.yaml | 1343 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1343 insertions(+) create mode 100644 docs/api/openapi.yaml diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml new file mode 100644 index 000000000..6f382aba2 --- /dev/null +++ b/docs/api/openapi.yaml @@ -0,0 +1,1343 @@ +--- +openapi: "3.0.0" +info: + title: LibreTime API overview + version: 1.1 +paths: + /live-info: + get: + summary: Retrieve the currently playing show as well as upcoming shows + parameters: + - name: api_key + in: path + description: The API key to use for authentication + required: false + - name: type + in: path + description: |- + endofday retrieves the info for shows up until the end of the day, + while interval will return shows in the next 48 hours + schema: + enum: + - 'endofday' + - 'interval' + default: 'interval' + required: false + - name: limit + in: path + description: the number of shows to retrieve + schema: + type: integer + default: 5 + required: false + responses: + '200': + description: 200 response for default request + content: + application/json: + example: { + "env": "production", + "schedulerTime": "2019-10-21 17:52:45", + "previous": { + "starts": "2019-10-21 17:47:25.000000", + "ends": "2019-10-21 17:52:13.000000", + "type": "track", + "name": "Disclosure - F For You (feat. Mary J. Blige)", + "metadata": { + "id": 8, + "name": "", + "mime": "audio/mp3", + "ftype": "audioclip", + "directory": 1, + "filepath": "imported/1/Disclosure/www.mmibty.com/01-F-For-You-feat.-Mary-J.-Blige.mp3", + "import_status": 0, + "currentlyaccessing": 0, + "editedby": null, + "mtime": "2019-10-21 17:19:03", + "utime": "2019-10-21 17:18:57", + "lptime": "2019-10-21 17:47:25", + "md5": "e008616551750aea49820a16d1fb1527", + "track_title": "F For You (feat. Mary J. Blige)", + "artist_name": "Disclosure", + "bit_rate": 251628, + "sample_rate": 44100, + "format": null, + "length": "00:04:48.026122", + "album_title": "www.mmibty.com", + "genre": "Electronic", + "comments": null, + "year": "2014", + "track_number": 1, + "channels": 2, + "url": null, + "bpm": null, + "rating": null, + "encoded_by": null, + "disc_number": null, + "mood": null, + "label": null, + "composer": null, + "encoder": null, + "checksum": null, + "lyrics": null, + "orchestra": null, + "conductor": null, + "lyricist": null, + "original_lyricist": null, + "radio_station_name": null, + "info_url": null, + "artist_url": null, + "audio_source_url": null, + "radio_station_url": null, + "buy_this_url": null, + "isrc_number": null, + "catalog_number": null, + "original_artist": null, + "copyright": null, + "report_datetime": null, + "report_location": null, + "report_organization": null, + "subject": null, + "contributor": null, + "language": null, + "soundcloud_id": null, + "soundcloud_error_code": null, + "soundcloud_error_msg": null, + "soundcloud_link_to_file": null, + "soundcloud_upload_time": null, + "replay_gain": "-5.58", + "owner_id": 1, + "cuein": "00:00:00", + "cueout": "00:04:48.026122", + "hidden": false, + "filesize": 9271626, + "description": null, + "artwork": "imported/1/artwork/01-F-For-You-feat.-Mary-J.-Blige", + "artwork_url": "http://localhost:8080/api/track?id=8&return=artwork" + } + }, + "current": { + "starts": "2019-10-21 17:52:13", + "ends": "2019-10-21 17:56:27", + "type": "track", + "name": "Armin van Buuren - Ping Pong", + "media_item_played": true, + "metadata": { + "id": 2, + "name": "", + "mime": "audio/mp3", + "ftype": "audioclip", + "directory": 1, + "filepath": "imported/1/Armin van Buuren/A State of Trance 2014/2-18 Armin van Buuren - Ping Pong.mp3", + "import_status": 0, + "currentlyaccessing": 0, + "editedby": null, + "mtime": "2019-10-21 17:18:02", + "utime": "2019-10-21 17:18:00", + "lptime": "2019-10-21 17:52:13", + "md5": "04c26823902065db0706d121d0e703a2", + "track_title": "Ping Pong", + "artist_name": "Armin van Buuren", + "bit_rate": 32000, + "sample_rate": 44100, + "format": null, + "length": "00:04:14.171429", + "album_title": "A State of Trance 2014", + "genre": "Trance;Electronic;Dance", + "comments": null, + "year": "2014", + "track_number": 18, + "channels": 2, + "url": null, + "bpm": null, + "rating": null, + "encoded_by": null, + "disc_number": null, + "mood": null, + "label": "Armada Music", + "composer": null, + "encoder": null, + "checksum": null, + "lyrics": null, + "orchestra": null, + "conductor": null, + "lyricist": null, + "original_lyricist": null, + "radio_station_name": null, + "info_url": null, + "artist_url": null, + "audio_source_url": null, + "radio_station_url": null, + "buy_this_url": null, + "isrc_number": null, + "catalog_number": null, + "original_artist": null, + "copyright": null, + "report_datetime": null, + "report_location": null, + "report_organization": null, + "subject": null, + "contributor": null, + "language": null, + "soundcloud_id": null, + "soundcloud_error_code": null, + "soundcloud_error_msg": null, + "soundcloud_link_to_file": null, + "soundcloud_upload_time": null, + "replay_gain": "-5.07", + "owner_id": 1, + "cuein": "00:00:00", + "cueout": "00:04:14.171429", + "hidden": false, + "filesize": 6136238, + "description": null, + "artwork": "imported/1/artwork/2-18 Armin van Buuren - Ping Pong", + "artwork_url": "http://localhost:8080/api/track?id=2&return=artwork" + }, + "record": "0" + }, + "next": { + "starts": "2019-10-21 17:56:27.000000", + "ends": "2019-10-21 18:00:28.000000", + "type": "track", + "name": "Bastille - No Angels (feat. Ella)", + "metadata": { + "id": 4, + "name": "", + "mime": "audio/mp3", + "ftype": "audioclip", + "directory": 1, + "filepath": "imported/1/Bastille/Other People's Heartache, Pt. 2/03 Bastille - No Angels (feat. Ella).mp3", + "import_status": 0, + "currentlyaccessing": 0, + "editedby": null, + "mtime": "2019-10-21 17:18:16", + "utime": "2019-10-21 17:18:14", + "lptime": "2019-10-21 17:24:46", + "md5": "87bf83451d7618eefc0141c262aead2a", + "track_title": "No Angels (feat. Ella)", + "artist_name": "Bastille", + "bit_rate": 128000, + "sample_rate": 44100, + "format": null, + "length": "00:04:00.752438", + "album_title": "Other People's Heartache, Pt. 2", + "genre": null, + "comments": null, + "year": "2012", + "track_number": 3, + "channels": 2, + "url": null, + "bpm": null, + "rating": null, + "encoded_by": null, + "disc_number": null, + "mood": null, + "label": "[no label]", + "composer": null, + "encoder": null, + "checksum": null, + "lyrics": null, + "orchestra": null, + "conductor": null, + "lyricist": null, + "original_lyricist": null, + "radio_station_name": null, + "info_url": null, + "artist_url": null, + "audio_source_url": null, + "radio_station_url": null, + "buy_this_url": null, + "isrc_number": null, + "catalog_number": null, + "original_artist": null, + "copyright": null, + "report_datetime": null, + "report_location": null, + "report_organization": null, + "subject": null, + "contributor": null, + "language": null, + "soundcloud_id": null, + "soundcloud_error_code": null, + "soundcloud_error_msg": null, + "soundcloud_link_to_file": null, + "soundcloud_upload_time": null, + "replay_gain": "-8.57", + "owner_id": 1, + "cuein": "00:00:00", + "cueout": "00:04:00.752438", + "hidden": false, + "filesize": 3858688, + "description": null, + "artwork": "" + } + }, + "currentShow": [ + { + "start_timestamp": "2019-10-21 17:20:00", + "end_timestamp": "2019-10-21 18:31:00", + "name": "Show 1", + "description": "A show", + "id": 1, + "instance_id": 1, + "record": 0, + "url": "https://example.com", + "image_path": "", + "starts": "2019-10-21 17:20:00", + "ends": "2019-10-21 18:31:00" + } + ], + "nextShow": [ + { + "id": 2, + "instance_id": 2, + "name": "Reading", + "description": "A reading of After the EMP by Harley Tate", + "url": "https://example.com", + "start_timestamp": "2019-10-21 18:31:00", + "end_timestamp": "2019-10-22 10:45:00", + "starts": "2019-10-21 18:31:00", + "ends": "2019-10-22 10:45:00", + "record": 0, + "image_path": "", + "type": "show" + } + ], + "source_enabled": "Scheduled", + "timezone": "UTC", + "timezoneOffset": "0", + "AIRTIME_API_VERSION": "1.1" + } + /live-info-v2: + get: + summary: Retrieve the currently playing and upcoming shows + parameters: + - name: api_key + in: path + description: The API key to use for authentication + required: true + - name: days + in: path + description: The number of days to return + schema: + type: integer + default: 2 + required: false + - show_id: shows + in: path + description: the number of shows to retrieve + schema: + type: integer + default: 5 + required: false + - name: timezone + in: path + description: The timezone to send the times in + schema: + type: string + default: "$server_timezone" + required: false + responses: + '200': + description: 200 response for default request + content: + application/json: + example: { + "station": { + "env": "production", + "schedulerTime": "2019-10-21 17:29:40", + "source_enabled": "Scheduled", + "timezone": "UTC", + "AIRTIME_API_VERSION": "1.1" + }, + "tracks": { + "previous": { + "starts": "2019-10-21 17:24:45", + "ends": "2019-10-21 17:28:46", + "type": "track", + "name": "Bastille - No Angels (feat. Ella)", + "metadata": { + "id": 4, + "name": "", + "mime": "audio/mp3", + "ftype": "audioclip", + "directory": 1, + "filepath": "imported/1/Bastille/Other People's Heartache, Pt. 2/03 Bastille - No Angels (feat. Ella).mp3", + "import_status": 0, + "currentlyaccessing": 0, + "editedby": null, + "mtime": "2019-10-21 17:18:16", + "utime": "2019-10-21 17:18:14", + "lptime": "2019-10-21 17:24:46", + "md5": "87bf83451d7618eefc0141c262aead2a", + "track_title": "No Angels (feat. Ella)", + "artist_name": "Bastille", + "bit_rate": 128000, + "sample_rate": 44100, + "format": null, + "length": "00:04:00.752438", + "album_title": "Other People's Heartache, Pt. 2", + "genre": null, + "comments": null, + "year": "2012", + "track_number": 3, + "channels": 2, + "url": null, + "bpm": null, + "rating": null, + "encoded_by": null, + "disc_number": null, + "mood": null, + "label": "[no label]", + "composer": null, + "encoder": null, + "checksum": null, + "lyrics": null, + "orchestra": null, + "conductor": null, + "lyricist": null, + "original_lyricist": null, + "radio_station_name": null, + "info_url": null, + "artist_url": null, + "audio_source_url": null, + "radio_station_url": null, + "buy_this_url": null, + "isrc_number": null, + "catalog_number": null, + "original_artist": null, + "copyright": null, + "report_datetime": null, + "report_location": null, + "report_organization": null, + "subject": null, + "contributor": null, + "language": null, + "soundcloud_id": null, + "soundcloud_error_code": null, + "soundcloud_error_msg": null, + "soundcloud_link_to_file": null, + "soundcloud_upload_time": null, + "replay_gain": "-8.57", + "owner_id": 1, + "cuein": "00:00:00", + "cueout": "00:04:00.752438", + "hidden": false, + "filesize": 3858688, + "description": null, + "artwork": "" + } + }, + "current": null, + "next": { + "starts": "2019-10-21 17:32:49", + "ends": "2019-10-21 17:36:44", + "type": "track", + "name": "Bob Marley - Could You Be Loved", + "metadata": { + "id": 14, + "name": "", + "mime": "audio/mp3", + "ftype": "audioclip", + "directory": 1, + "filepath": "imported/1/Bob Marley/Greatest Hits/02. Could You Be Loved.mp3", + "import_status": 0, + "currentlyaccessing": 0, + "editedby": null, + "mtime": "2019-10-21 17:19:16", + "utime": "2019-10-21 17:18:59", + "lptime": null, + "md5": "75e49569fd6af61cc8c18f5660beadc2", + "track_title": "Could You Be Loved", + "artist_name": "Bob Marley", + "bit_rate": 128000, + "sample_rate": 44100, + "format": null, + "length": "00:03:55.11", + "album_title": "Greatest Hits", + "genre": "Various", + "comments": null, + "year": null, + "track_number": 2, + "channels": 2, + "url": null, + "bpm": 103, + "rating": null, + "encoded_by": null, + "disc_number": null, + "mood": null, + "label": null, + "composer": null, + "encoder": null, + "checksum": null, + "lyrics": null, + "orchestra": null, + "conductor": null, + "lyricist": null, + "original_lyricist": null, + "radio_station_name": null, + "info_url": null, + "artist_url": null, + "audio_source_url": null, + "radio_station_url": null, + "buy_this_url": null, + "isrc_number": null, + "catalog_number": null, + "original_artist": null, + "copyright": null, + "report_datetime": null, + "report_location": null, + "report_organization": null, + "subject": null, + "contributor": null, + "language": null, + "soundcloud_id": null, + "soundcloud_error_code": null, + "soundcloud_error_msg": null, + "soundcloud_link_to_file": null, + "soundcloud_upload_time": null, + "replay_gain": "-1.2", + "owner_id": 1, + "cuein": "00:00:00", + "cueout": "00:03:55.11", + "hidden": false, + "filesize": 3773820, + "description": null, + "artwork": "" + } + } + }, + "shows": { + "previous": [], + "current": { + "name": "Show 1", + "description": "A show", + "genre": "HipHop", + "id": 1, + "instance_id": 1, + "record": 0, + "url": "https://example.com", + "image_path": "", + "starts": "2019-10-21 17:20:00", + "ends": "2019-10-21 18:31:00" + }, + "next": [ + { + "name": "Reading", + "description": "A reading of After the EMP by Harley Tate", + "genre": "Sci-fi", + "id": 2, + "instance_id": 2, + "record": 0, + "url": "https://example.com", + "image_path": "", + "starts": "2019-10-21 18:31:00", + "ends": "2019-10-22 10:45:00" + } + ] + }, + "sources": { + "livedj": "off", + "masterdj": "off", + "scheduledplay": "on" + } + } + /week-info: + get: + summary: Retrieve the schedule for the week + parameters: + - name: api_key + in: path + description: The API key to use for authentication + required: false + responses: + '200': + description: 200 response for default request + content: + application/json: + example: { + "monday": [ + { + "start_timestamp": "2019-10-21 17:20:00", + "end_timestamp": "2019-10-21 18:31:00", + "name": "Show 1", + "description": "A show", + "id": 1, + "instance_id": 1, + "instance_description": "", + "record": 0, + "url": "https://example.com", + "image_path": "", + "starts": "2019-10-21 17:20:00", + "ends": "2019-10-21 18:31:00" + }, + { + "start_timestamp": "2019-10-21 18:31:00", + "end_timestamp": "2019-10-22 10:45:00", + "name": "Reading", + "description": "A reading of After the EMP by Harley Tate", + "id": 2, + "instance_id": 2, + "instance_description": "", + "record": 0, + "url": "https://example.com", + "image_path": "", + "starts": "2019-10-21 18:31:00", + "ends": "2019-10-22 10:45:00" + } + ], + "tuesday": [], + "wednesday": [], + "thursday": [], + "friday": [], + "saturday": [], + "sunday": [], + "nextmonday": [], + "nexttuesday": [], + "nextwednesday": [], + "nextthursday": [], + "nextfriday": [], + "nextsaturday": [], + "nextsunday": [], + "AIRTIME_API_VERSION": "1.1" + } + /station-metadata: + get: + summary: BROKEN - Retrieve the schedule for the week + parameters: + - name: api_key + in: path + description: The API key to use for authentication + required: false + /station-logo: + get: + summary: Fetch the station logo + parameters: + - name: api_key + in: path + description: The API key to use for authentication + required: false + /show-history-feed: + get: + summary: BROKEN - Retrieve the show shedules for a given time range and show + parameters: + - name: api_key + in: path + description: The API key to use for authentication + required: false + - name: start + in: path + description: The start time for the feed + required: true + - name: end + in: path + description: The end time for the feed + required: true + - name: timezone + in: path + description: The timezone that the times are in + required: true + /item-history-feed: + get: + summary: Retrieve the items for a time range and/or show + parameters: + - name: api_key + in: path + description: The API key to use for authentication + required: false + - name: start + in: path + description: The start time for the feed + required: false + - name: end + in: path + description: The end time for the feed + required: false + - name: timezone + in: path + description: The timezone that the times are in + required: false + - name: instance_id + in: path + description: The show instance ID + required: false + responses: + '200': + description: The 200 default response + content: + application/json: + example: [ + { + "starts": "2019-10-21 18:19:07", + "ends": "2019-10-21 18:23:55", + "history_id": 16, + "instance_id": 1, + "track_title": "F For You (feat. Mary J. Blige)", + "artist_name": "Disclosure", + "checkbox": "" + }, + { + "starts": "2019-10-21 17:20:31", + "ends": "2019-10-21 17:24:45", + "history_id": 1, + "instance_id": 1, + "track_title": "Ping Pong", + "artist_name": "Armin van Buuren", + "checkbox": "" + }, + ] + /shows: + get: + summary: Retrieve the show info (without schedule for given show_id + parameters: + - name: api_key + in: path + description: The API key to use for authentication + required: false + - name: show_id + in: path + description: The ID of the show + required: false + response: + '200': + description: The response with a show_id of 1 + content: + application/json: + example: [ + { + "name": "Show 1", + "id": 1, + "url": "https://example.com", + "genre": "HipHop", + "description": "A show", + "color": "", + "background_color": "", + "linked": false, + "has_autoplaylist": false, + "autoplaylist_id": null, + "autoplaylist_repeat": false + } + ] + /show-tracks: + get: + summary: Display the track listing for given instance_id + parameters: + - name: api_key + in: path + description: The API key to use for authentication + required: false + - name: instance_id + in: path + description: The ID of the show + required: true + response: + '200': + description: The response with a instance_id of 1 + content: + application/json: + example: [ + { + "title": "Ping Pong", + "artist": "Armin van Buuren", + "position": 0, + "id": 1, + "mime": "audio/mp3", + "starts": "2019-10-21 17:20:31", + "length": "4:14.2", + "file_id": 2 + }, + { + "title": "No Angels (feat. Ella)", + "artist": "Bastille", + "position": 1, + "id": 2, + "mime": "audio/mp3", + "starts": "2019-10-21 17:24:45", + "length": "4:00.8", + "file_id": 4 + } + ] + /show-schedules: + get: + summary: Display the show schedule for given show_id + parameters: + - name: api_key + in: path + description: The API key to use for authentication + required: false + - name: show_id + in: path + description: The ID of the show + required: true + - name: start + in: path + description: The start time for the feed + required: false + - name: end + in: path + description: The end time for the feed + required: false + - name: timezone + in: path + description: The timezone that the times are in + required: false + response: + '200': + description: The response with a instance_id of 1 + content: + application/json: + example: [ + { + "starts": "2019-10-21 17:20:00", + "ends": "2019-10-21 18:31:00", + "record": 0, + "rebroadcast": 0, + "parent_starts": null, + "record_id": null, + "show_id": 1, + "name": "Show 1", + "description": "A show", + "color": "", + "background_color": "", + "image_path": "", + "linked": false, + "file_id": null, + "instance_id": 1, + "instance_description": "", + "created": "2019-10-21 17:20:22", + "last_scheduled": "2019-10-21 17:20:50", + "time_filled": "01:14:39.265872", + "soundcloud_id": null + } + ] + /show-logo: + get: + summary: Fetch the show logo. Returns the station logo if none exists + parameters: + - name: api_key + in: path + description: The API key to use for authentication + required: false + - name: id + in: path + description: The ID of the show with the logo to retrieve + required: true + /track: + get: + summary: Displays the metadata of a particular track + parameters: + - name: api_key + in: path + description: The API key to use for authentication + required: false + - name: id + in: path + description: The track id + required: true + - name: response + in: path + description: The type of response + schema: + enum: + - json + - artwork_data + - artwork + required: true + responses: + '200': + description: The 200 response + content: + application/json: + example: { + "MDATA_KEY_FILEPATH": "imported\/1\/Armin van Buuren\/Another You (feat. Mr. Probz)\/01 Another You (feat. Mr. Probz).mp3", + "MDATA_KEY_DIRECTORY": 1, + "MDATA_KEY_TITLE": "Another You (feat. Mr. Probz)", + "MDATA_KEY_CREATOR": "Armin van Buuren", + "MDATA_KEY_SOURCE": "Another You (feat. Mr. Probz)", + "MDATA_KEY_DURATION": "00:03:19.183673", + "MDATA_KEY_MIME": "audio\/mp3", + "MDATA_KEY_FTYPE": "audioclip", + "MDATA_KEY_URL": null, + "MDATA_KEY_GENRE": null, + "MDATA_KEY_MOOD": null, + "MDATA_KEY_LABEL": "Armin Audio B.V.", + "MDATA_KEY_COMPOSER": null, + "MDATA_KEY_DESCRIPTION": null, + "MDATA_KEY_SAMPLERATE": 44100, + "MDATA_KEY_BITRATE": 192000, + "MDATA_KEY_ENCODER": null, + "MDATA_KEY_ISRC": null, + "MDATA_KEY_COPYRIGHT": null, + "MDATA_KEY_YEAR": "2015", + "MDATA_KEY_BPM": null, + "MDATA_KEY_TRACKNUMBER": 1, + "MDATA_KEY_CONDUCTOR": null, + "MDATA_KEY_LANGUAGE": null, + "MDATA_KEY_REPLAYGAIN": "-8.36", + "MDATA_KEY_OWNER_ID": 1, + "MDATA_KEY_CUE_IN": "00:00:00", + "MDATA_KEY_CUE_OUT": "00:03:19.183673", + "MDATA_KEY_ARTWORK": "imported\/1\/artwork\/01 Another You (feat. Mr. Probz)" + } + /stream-m3u: + get: + summary: Returns m3u playlist file for the station's output stream + response: + '200': + description: The M3U file for the stream + content: application/x-mpegurl + /version: + get: + summary: Returns the current LibreTime and API versions + parameters: + - name: api_key + in: path + description: The API key to use for authentication + required: false + responses: + '200': + description: 200 response + content: + application/json: + example: { + "airtime_version": "3.0.0~alpha.5", + "api_version": "1.1" + } + /recorded-shows: + get: + summary: BROKEN - Unclear what this did, not implemented in ApiController + /calendar-init: + get: + summary: BROKEN - Unclear what this did, not implemented in ApiController + /upload-file: + get: + summary: BROKEN - Unclear what this did, not implemented in ApiController + /upload-recorded: + post: + summary: Upload a recorded show + parameters: + - name: showinstanceid + in: path + description: The ID of the show that was recorded + required: true + - name: fileid + in: path + description: The ID of the recorded file in the database + required: true + - name: api_key + in: path + description: The API key to use for authentication + required: true + /media-monitor-setup: + post: + summary: Initialises monitoring of media directories + parameters: + - name: api_key + in: path + description: The API key to use for authentication + required: true + /media-item-status: + get: + summary: BROKEN - Unclear what this did, not implemented in ApiController + parameters: + - name: api_key + in: path + description: The API key to use for authentication + required: true + /reload-metadata: + get: + summary: BROKEN - Unclear what this did, not implemented in ApiController + parameters: + - name: api_key + in: path + description: The API key to use for authentication + required: true + /list-all-files: + get: + summary: BROKEN - List all files in a given directory managed by LibreTime + parameters: + - name: dir_id + in: path + description: The directory to list files in + required: true + - name: all + in: path + description: |- + true to show all files in the database, even if they do not exist + on disk + required: false + - name: api_key + in: path + description: The API key to use for authentication + required: true + /list-all-watched-dirs: + get: + summary: |- + BROKEN (LT does not currently support watched files) - lists all + directories to watch for new files + parameters: + - name: api_key + in: path + description: The API key to use for authentication + required: true + /add-watched-dir: + post: + summary: |- + BROKEN (LT does not currently support watched files) - adds a directory + to the list of watched directories + parameters: + - name: path + in: path + description: the path of the directory on the server, base64 encoded + required: true + - name: api_key + in: path + description: The API key to use for authentication + required: true + /remove-watched-dir: + post: + summary: |- + BROKEN (LT does not currently support watched files) - removes a + directory from the list of watched directories + parameters: + - name: path + in: path + description: the path of the directory on the server, base64 encoded + required: true + - name: api_key + in: path + description: The API key to use for authentication + required: true + /set-storage-dir: + post: + summary: Sets the storage path for music files + parameters: + - name: path + in: path + description: The base64 encoded path to the new storage directory + required: true + - name: api_key + in: path + description: The API key to use for authentication + required: true + /get-stream-setting: + get: + summary: |- + BROKEN - Returns the settings configured for the four output Icecast + streams + parameters: + - name: api_key + in: path + description: The API key to use for authentication + required: true + /status: + get: + summary: |- + BROKEN - Returns the current status of the various LibreTime + components + parameters: + - name: api_key + in: path + description: The API key to use for authentication + required: true + /register-component: + post: + summary: Add a service component (for example Monit) + parameters: + - name: component + in: path + description: The component name to add + required: true + - name: api_key + in: path + description: The API key to use for authentication + required: true + /update-liquidsoap-status: + post: + summary: |- + Update the status of a stream to indicate a Liquidsoap status update + parameters: + - name: msg_post + in: path + description: The error message to use + required: true + - name: stream_id + in: path + description: The ID of the stream to update + required: true + - name: boot_time + description: |- + The time that the status was last updated. Excluding this will + force update. + required: false + - name: api_key + in: path + description: The API key to use for authentication + required: true + /update-file-system-mount: + post: + summary: |- + Handles additions/deletions of mount points on which watched + directories reside + parameters: + - name: added_dir + in: path + description: |- + A comma separated list of directories that were added to the system + required: false + - name: removed_dir + in: path + description: |- + A comma separated list of directories that were removed from the + system + required: false + - name: api_key + in: path + description: The API key to use for authentication + required: true + /handle-watched-dir-missing: + post: + summary: |- + BROKEN (LibreTime does not currentl handle watched directories) - + Handles missing watched directories. + parameters: + - name: dir + in: path + description: The directory to disable + required: true + - name: api_key + in: path + description: The API key to use for authentication + required: true + /rabbitmq-do-push: + post: + summary: |- + Used by dev scripts to make rabbitmq send out a message to pypo that a + potential change has been made to the database. + parameters: + - name: api_key + in: path + description: The API key to use for authentication + required: true + /check-live-stream-auth: + get: + summary: |- + Tests the authentication supplied to authenticate DJs connecting to a + live stream + parameters: + - name: api_key + in: path + description: The API key to use for authentication + required: true + requestBody: + required: true + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + username: + type: string + password: + type: string + format: password + djtype: + type: string + required: + - username + - password + - djtype + /update-source-status: + post: + summary: Update on source connect or disconnect + parameters: + - name: sourcename + in: path + description: |- + The name of the source that has connected or disconnected + - name: status + in: path + description: true if the source is now connected + - name: api_key + in: path + description: The API key to use for authentication + required: true + /get-bootstrap-info: + get: + summary: |- + BROKEN - Retrieves the current state of the instance. This includes + which sources are currently connected, station name, etc + parameters: + - name: api_key + in: path + description: The API key to use for authentication + required: true + /get-files-without-replay-gain: + get: + summary: |- + BROKEN (returns all files) - Returns the files that do not have replay + gain set. + parameters: + - name: dir_id + in: path + description: The directory ID + - name: api_key + in: path + description: The API key to use for authentication + required: true + /get-files-without-silan-value: + get: + summary: Returns the files that have not been processed by silan + parameters: + - name: api_key + in: path + description: The API key to use for authentication + required: true + responses: + '200': + description: 200 response for default request + content: + application/json: + example: [ + { + "id": 9, + "fp": "/srv/airtime/stor/" + }, + { + "id": 12, + "fp": "/srv/airtime/stor/imported/1/Sam Smith Feat John Legend/The Official Uk Top 40 Singles Chart 03-22-2015/01 Sam Smith Feat John Legend - Lay Me Down.mp3" + }, + { + "id": 13, + "fp": "/srv/airtime/stor/imported/1/Mumford & Sons/Wilder Mind [ Deluxe Edition ]/01 - Tompkins Square Park.mp3" + }, + { + "id": 3, + "fp": "/srv/airtime/stor/imported/1/Bastille/All This Bad Blood/1-02 Things We Lost in the Fire.mp3" + }, + { + "id": 1, + "fp": "/srv/airtime/stor/imported/1/Armin van Buuren/Another You (feat. Mr. Probz)/01 Another You (feat. Mr. Probz).mp3" + }, + { + "id": 15, + "fp": "/srv/airtime/stor/imported/1/Harley Tate/Harley Tate - After the EMP 01 - After the EMP/Harley Tate - After the EMP 01 - After the EMP.mp3" + } + ] + /reload-metadata-group: + get: + summary: |- + Extracts all file metadata from the list of files + parameters: + - name: mdXXX + # This could be wrong - the function in ApiController is confusing... + in: path + description: |- + A json encoded hash with all the information related to the action. + the XXX represents at least 1 digit. Currently the mdXXX key has no + meaning. + - name: api_key + in: path + description: The API key to use for authentication + required: true + /notify-webstream-data: + post: + summary: Notifies webstreams of data being updated + parameters: + - name: data + in: path + description: |- + A json encoded hash with the data used to notify the webstream. This + includes keys such as 'title'. + required: true + - name: media_id + in: path + description: The ID of the media to show + required: true + - name: api_key + in: path + description: The API key to use for authentication + required: true + /get-stream-parameters: + get: + summary: BROKEN - Retrieves the parameters set for each stream + parameters: + - name: api_key + in: path + description: The API key to use for authentication + required: true + /push-stream-stats: + post: + summary: Updates the listener number statistics + parameters: + - name: data + in: path + description: A json encoded mapping of time to number of listeners + required: true + - name: api_key + in: path + description: The API key to use for authentication + required: true + /update-stream-setting-table: + post: + summary: Set stream settings + parameters: + - name: data + in: path + description: A json encoded array of key-value pairs to update + required: true + - name: api_key + in: path + description: The API key to use for authentication + required: true + /update-replay-gain-value: + post: + summary: Updates the replay gain values for media items + parameters: + - name: api_key + in: path + description: The API key to use for authentication + required: true + - name: data + description: A json encoded array of media ID and gain pairs + required: true + /update-cue-values-by-silan: + post: + summary: Updates the silan cue values for media items + parameters: + - name: api_key + in: path + description: The API key to use for authentication + required: true + - name: data + description: A json encoded array of media ID and cue pairs + required: true + /get-usability-hint: + get: + summary: Returns the usibility hint tool-tip for a UI item + parameters: + - name: api_key + in: path + description: The API key to use for authentication + required: true + - name: userPath + in: path + description: The URL path to the UI item + required: true + /poll-celery: + post: + summary: Polls celery for tasks + parameters: + - name: api_key + in: path + description: The API key to use for authentication + required: true + /recalculate-schedule: + post: + summary: Recalculates the schedule to remove gaps and update timing + parameters: + - name: api_key + in: path + description: The API key to use for authentication + required: true From 556d4eb7b99fe59de1d18076eb730411096ea60d Mon Sep 17 00:00:00 2001 From: Roberto Soto Date: Fri, 22 Nov 2019 21:01:39 -0800 Subject: [PATCH 02/23] When live streaming to LibreTime on ports 8001 or 8002, the MVC interface would break (500) I've found this is because of ScheduleController L305, introduced recently in https://github.com/LibreTime/libretime/commit/45dbf8475060e14a56dae4d9389ff8546a69bc09 A simple if clause allows us to workaround the issue, which must be that live streams don't have pictures nor metadata... Fixes #919 --- airtime_mvc/application/controllers/ScheduleController.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/airtime_mvc/application/controllers/ScheduleController.php b/airtime_mvc/application/controllers/ScheduleController.php index bf9d0fa63..54482da8c 100644 --- a/airtime_mvc/application/controllers/ScheduleController.php +++ b/airtime_mvc/application/controllers/ScheduleController.php @@ -302,8 +302,10 @@ class ScheduleController extends Zend_Controller_Action $range["previous"]["ends"] = Application_Common_DateHelper::UTCStringToUserTimezoneString($range["previous"]["ends"]); } if (isset($range["current"])) { - $get_artwork = FileDataHelper::getArtworkData($range["current"]["metadata"]["artwork"], 256); - $range["current"]["metadata"]["artwork_data"] = $get_artwork; + if (isset($range["current"]["metadata"])) { + $get_artwork = FileDataHelper::getArtworkData($range["current"]["metadata"]["artwork"], 256); + $range["current"]["metadata"]["artwork_data"] = $get_artwork; + } $range["current"]["starts"] = Application_Common_DateHelper::UTCStringToUserTimezoneString($range["current"]["starts"]); $range["current"]["ends"] = Application_Common_DateHelper::UTCStringToUserTimezoneString($range["current"]["ends"]); } From 342154fe41a9e35c9c9149dcc82b6bf9264fdcd7 Mon Sep 17 00:00:00 2001 From: Roberto Soto Date: Wed, 4 Dec 2019 03:42:34 -0800 Subject: [PATCH 03/23] basic check of existing property in dashboard.js fixes #924 --- airtime_mvc/public/js/airtime/dashboard/dashboard.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/airtime_mvc/public/js/airtime/dashboard/dashboard.js b/airtime_mvc/public/js/airtime/dashboard/dashboard.js index 1b4d1c468..555cc4c7b 100644 --- a/airtime_mvc/public/js/airtime/dashboard/dashboard.js +++ b/airtime_mvc/public/js/airtime/dashboard/dashboard.js @@ -130,8 +130,7 @@ function updatePlaybar(){ $('#current').html(""+$.i18n._("Recording:")+""+currentSong.name+","); } else { $('#current').text(currentSong.name+","); - - if (currentSong.metadata.artwork_data) { + if (currentSong.metadata && currentSong.metadata.artwork_data) { var check_current_song = Cookies.get('current_track'); var loaded = Cookies.get('loaded'); From 1139f82ef2c159d9d96acaada37566ab44324cb3 Mon Sep 17 00:00:00 2001 From: Robb Ebright Date: Sat, 28 Dec 2019 09:01:22 -0500 Subject: [PATCH 04/23] fix rasbian debian 10 install --- install | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/install b/install index 1dcd695b5..df19815fb 100755 --- a/install +++ b/install @@ -633,6 +633,15 @@ case "${dist}-${code}" in is_debian_stretch=true ;; #End of fix + #Fix for Raspbian 10 (buster) + raspbian-10|10) + code="buster" + dist="debian" + is_debian_dist=true + is_debian_buster=true + ;; + #End of fix + debian-8|debian-jessie) echo -e "ERROR: Debian Jessie is archived and does not receive any security or other updates since 2018-05-17." >&2 echo -e "The LibreTime installer dropped support for installing LibreTime on Jessie in 3.0.0-alpha.8." >&2 From 0931ba842a6d2999de7ecc3c705ae2700921d1bf Mon Sep 17 00:00:00 2001 From: Robb Ebright Date: Mon, 30 Dec 2019 22:07:48 -0500 Subject: [PATCH 05/23] add legacy script for liquidsoap 1.1.1 compatibility --- python_apps/pypo/liquidsoap/__main__.py | 11 +- .../pypo/liquidsoap/liquidsoap_auth.py | 4 +- python_apps/pypo/liquidsoap/ls_lib.liq | 6 +- python_apps/pypo/liquidsoap/ls_lib_legacy.liq | 399 ++++++++++++++++ python_apps/pypo/liquidsoap/ls_script.liq | 50 +- .../pypo/liquidsoap/ls_script_legacy.liq | 445 ++++++++++++++++++ 6 files changed, 890 insertions(+), 25 deletions(-) create mode 100644 python_apps/pypo/liquidsoap/ls_lib_legacy.liq create mode 100644 python_apps/pypo/liquidsoap/ls_script_legacy.liq diff --git a/python_apps/pypo/liquidsoap/__main__.py b/python_apps/pypo/liquidsoap/__main__.py index 54463b64c..09e4abe5f 100644 --- a/python_apps/pypo/liquidsoap/__main__.py +++ b/python_apps/pypo/liquidsoap/__main__.py @@ -5,6 +5,7 @@ import argparse import os import generate_liquidsoap_cfg import logging +import subprocess PYPO_HOME = '/var/tmp/airtime/pypo/' @@ -21,11 +22,15 @@ def run(): logging.basicConfig(level=getattr(logging, 'DEBUG', None)) generate_liquidsoap_cfg.run() - script_path = os.path.join(os.path.dirname(__file__), 'ls_script.liq') - + ''' check liquidsoap version if less than 1.3 use legacy liquidsoap script ''' + liquidsoap_version=subprocess.check_output("liquidsoap --version", shell=True) + if "1.1.1" not in liquidsoap_version: + script_path = os.path.join(os.path.dirname(__file__), 'ls_script.liq') + else: + script_path = os.path.join(os.path.dirname(__file__), 'ls_script_legacy.liq') if args.debug: os.execl('/usr/bin/liquidsoap', 'airtime-liquidsoap', script_path, '--verbose', '-f', '--debug') else: os.execl('/usr/bin/liquidsoap', 'airtime-liquidsoap', script_path, '--verbose', '-f') -run() \ No newline at end of file +run() diff --git a/python_apps/pypo/liquidsoap/liquidsoap_auth.py b/python_apps/pypo/liquidsoap/liquidsoap_auth.py index 838898afb..fe4725305 100644 --- a/python_apps/pypo/liquidsoap/liquidsoap_auth.py +++ b/python_apps/pypo/liquidsoap/liquidsoap_auth.py @@ -15,7 +15,9 @@ elif dj_type == '--dj': response = api_clients.check_live_stream_auth(username, password, source_type) -if 'msg' in response: +if 'msg' in response and response['msg'] == True: print response['msg'] + sys.exit(0) else: print False + sys.exit(1) diff --git a/python_apps/pypo/liquidsoap/ls_lib.liq b/python_apps/pypo/liquidsoap/ls_lib.liq index b2822d596..1ed7495ea 100644 --- a/python_apps/pypo/liquidsoap/ls_lib.liq +++ b/python_apps/pypo/liquidsoap/ls_lib.liq @@ -266,7 +266,7 @@ def input.http_restart(~id,~initial_url="http://dummy/url") source = audio_to_stereo(input.http(buffer=5.,max=15.,id=id,autostart=false,initial_url)) def stopped() - "stopped" == list.hd(server.execute("#{id}.status")) + "stopped" == list.hd(server.execute("#{id}.status"), default="") end server.register(namespace=id, @@ -321,7 +321,7 @@ def cross_http(~debug=true,~http_input_id,source) cross_d = 3. def crosser(a,b) - url = list.hd(server.execute('#{id}.url')) + url = list.hd(server.execute('#{id}.url'), default="") status = list.hd(server.execute('#{id}.status')) on_m([("source_url",url)]) if debug then @@ -374,7 +374,7 @@ def http_fallback(~http_input_id,~http,~default) end def connected() - status = list.hd(server.execute("#{id}.status")) + status = list.hd(server.execute("#{id}.status"), default="") not(list.mem(status,["polling","stopped"])) end connected = gracetime(connected) diff --git a/python_apps/pypo/liquidsoap/ls_lib_legacy.liq b/python_apps/pypo/liquidsoap/ls_lib_legacy.liq new file mode 100644 index 000000000..b2822d596 --- /dev/null +++ b/python_apps/pypo/liquidsoap/ls_lib_legacy.liq @@ -0,0 +1,399 @@ +def notify(m) + command = "timeout --signal=KILL 45 pyponotify --media-id=#{m['schedule_table_id']} &" + log(command) + system(command) +end + +def notify_queue(m) + f = !dynamic_metadata_callback + ignore(f(m)) + notify(m) +end + +def notify_stream(m) + json_str = string.replace(pattern="\n",(fun (s) -> ""), json_of(m)) + #if a string has a single apostrophe in it, let's comment it out by ending the string before right before it + #escaping the apostrophe, and then starting a new string right after it. This is why we use 3 apostrophes. + json_str = string.replace(pattern="'",(fun (s) -> "'\''"), json_str) + command = "timeout --signal=KILL 45 pyponotify --webstream='#{json_str}' --media-id=#{!current_dyn_id} &" + + if !current_dyn_id != "-1" then + log(command) + system(command) + end +end + +# A function applied to each metadata chunk +def append_title(m) = + log("Using stream_format #{!stream_metadata_type}") + + if list.mem_assoc("mapped", m) then + #protection against applying this function twice. It shouldn't be happening + #and bug file with Liquidsoap. + m + else + if !stream_metadata_type == 1 then + [("title", "#{!show_name} - #{m['artist']} - #{m['title']}"), ("mapped", "true")] + elsif !stream_metadata_type == 2 then + [("title", "#{!station_name} - #{!show_name}"), ("mapped", "true")] + else + if "#{m['artist']}" == "" then + [("title", "#{m['title']}"), ("mapped", "true")] + else + [("title", "#{m['artist']} - #{m['title']}"), ("mapped", "true")] + end + end + end +end + +def crossfade_airtime(s) + #duration is automatically overwritten by metadata fields passed in + #with audio + s = fade.in(type="log", duration=0., s) + s = fade.out(type="log", duration=0., s) + fader = fun (a,b) -> add(normalize=false,[b,a]) + cross(fader,s) +end + +def transition(a,b) = + log("transition called...") + add(normalize=false, + [ sequence([ blank(duration=0.01), + fade.initial(duration=!default_dj_fade, b) ]), + fade.final(duration=!default_dj_fade, a) ]) +end + +# we need this function for special transition case(from default to queue) +# we don't want the trasition fade to have effect on the first song that would +# be played siwtching out of the default(silent) source +def transition_default(a,b) = + log("transition called...") + if !just_switched then + just_switched := false + add(normalize=false, + [ sequence([ blank(duration=0.01), + fade.initial(duration=!default_dj_fade, b) ]), + fade.final(duration=!default_dj_fade, a) ]) + else + just_switched := false + b + end +end + + +# Define a transition that fades out the +# old source, adds a single, and then +# plays the new source +def to_live(old,new) = + # Fade out old source + old = fade.final(old) + # Compose this in sequence with + # the new source + sequence([old,new]) +end + + +def output_to(output_type, type, bitrate, host, port, pass, mount_point, url, description, genre, user, s, stream, connected, name, channels) = + source = ref s + def on_error(msg) + connected := "false" + command = "timeout --signal=KILL 45 pyponotify --error='#{msg}' --stream-id=#{stream} --time=#{!time} &" + system(command) + log(command) + 5. + end + def on_connect() + connected := "true" + command = "timeout --signal=KILL 45 pyponotify --connect --stream-id=#{stream} --time=#{!time} &" + system(command) + log(command) + end + + stereo = (channels == "stereo") + + if output_type == "icecast" then + user_ref = ref user + if user == "" then + user_ref := "source" + end + output_mono = output.icecast(host = host, + port = port, + password = pass, + mount = mount_point, + fallible = true, + url = url, + description = description, + name = name, + genre = genre, + user = !user_ref, + on_error = on_error, + on_connect = on_connect) + + output_stereo = output.icecast(host = host, + port = port, + password = pass, + mount = mount_point, + fallible = true, + url = url, + description = description, + name = name, + genre = genre, + user = !user_ref, + on_error = on_error, + on_connect = on_connect) + if type == "mp3" then + %include "mp3.liq" + end + if type == "ogg" then + %include "ogg.liq" + end + + %ifencoder %opus + if type == "opus" then + %include "opus.liq" + end + %endif + + %ifencoder %fdkaac + if type == "aac" then + %include "fdkaac.liq" + end + %endif + else + user_ref = ref user + if user == "" then + user_ref := "source" + end + + output_mono = output.shoutcast(id = "shoutcast_stream_#{stream}", + host = host, + port = port, + password = pass, + fallible = true, + url = url, + genre = genre, + name = description, + user = !user_ref, + on_error = on_error, + on_connect = on_connect) + + output_stereo = output.shoutcast(id = "shoutcast_stream_#{stream}", + host = host, + port = port, + password = pass, + fallible = true, + url = url, + genre = genre, + name = description, + user = !user_ref, + on_error = on_error, + on_connect = on_connect) + + if type == "mp3" then + %include "mp3.liq" + end + + %ifencoder %fdkaac + if type == "aac" then + %include "fdkaac.liq" + end + %endif + end +end + +# Add a skip function to a source +# when it does not have one +# by default +#def add_skip_command(s) +# # A command to skip +# def skip(_) +# # get playing (active) queue and flush it +# l = list.hd(server.execute("queue.secondary_queue")) +# l = string.split(separator=" ",l) +# list.iter(fun (rid) -> ignore(server.execute("queue.remove #{rid}")), l) +# +# l = list.hd(server.execute("queue.primary_queue")) +# l = string.split(separator=" ", l) +# if list.length(l) > 0 then +# source.skip(s) +# "Skipped" +# else +# "Not skipped" +# end +# end +# # Register the command: +# server.register(namespace="source", +# usage="skip", +# description="Skip the current song.", +# "skip",fun(s) -> begin log("source.skip") skip(s) end) +#end + +def clear_queue(s) + source.skip(s) +end + +def set_dynamic_source_id(id) = + current_dyn_id := id + string_of(!current_dyn_id) +end + +def get_dynamic_source_id() = + string_of(!current_dyn_id) +end + +#cc-4633 + + +# NOTE +# A few values are hardcoded and may be dependent: +# - the delay in gracetime is linked with the buffer duration of input.http +# (delay should be a bit less than buffer) +# - crossing duration should be less than buffer length +# (at best, a higher duration will be ineffective) + +# HTTP input with "restart" command that waits for "stop" to be effected +# before "start" command is issued. Optionally it takes a new URL to play, +# which makes it a convenient replacement for "url". +# In the future, this may become a core feature of the HTTP input. +# TODO If we stop and restart quickly several times in a row, +# the data bursts accumulate and create buffer overflow. +# Flushing the buffer on restart could be a good idea, but +# it would also create an interruptions while the buffer is +# refilling... on the other hand, this would avoid having to +# fade using both cross() and switch(). +def input.http_restart(~id,~initial_url="http://dummy/url") + + source = audio_to_stereo(input.http(buffer=5.,max=15.,id=id,autostart=false,initial_url)) + + def stopped() + "stopped" == list.hd(server.execute("#{id}.status")) + end + + server.register(namespace=id, + "restart", + usage="restart [url]", + fun (url) -> begin + if url != "" then + log(string_of(server.execute("#{id}.url #{url}"))) + end + log(string_of(server.execute("#{id}.stop"))) + add_timeout(0.5, + { if stopped() then + log(string_of(server.execute("#{id}.start"))) ; + (-1.) + else 0.5 end}) + "OK" + end) + + # Dummy output should be useless if HTTP stream is meant + # to be listened to immediately. Otherwise, apply it. + # + # output.dummy(fallible=true,source) + + source + +end + +# Transitions between URL changes in HTTP streams. +def cross_http(~debug=true,~http_input_id,source) + + id = http_input_id + last_url = ref "" + change = ref false + + def on_m(m) + notify_stream(m) + changed = m["source_url"] != !last_url + log("URL now #{m['source_url']} (change: #{changed})") + if changed then + if !last_url != "" then change := true end + last_url := m["source_url"] + end + end + + # We use both metadata and status to know about the current URL. + # Using only metadata may be more precise is crazy corner cases, + # but it's also asking too much: the metadata may not pass through + # before the crosser is instantiated. + # Using only status in crosser misses some info, eg. on first URL. + source = on_metadata(on_m,source) + + cross_d = 3. + + def crosser(a,b) + url = list.hd(server.execute('#{id}.url')) + status = list.hd(server.execute('#{id}.status')) + on_m([("source_url",url)]) + if debug then + log("New track inside HTTP stream") + log(" status: #{status}") + log(" need to cross: #{!change}") + log(" remaining #{source.remaining(a)} sec before, \ + #{source.remaining(b)} sec after") + end + if !change then + change := false + # In principle one should avoid crossing on a live stream + # it'd be okay to do it here (eg. use add instead of sequence) + # because it's only once per URL, but be cautious. + sequence([fade.out(duration=cross_d,a),fade.in(b)]) + else + # This is done on tracks inside a single stream. + # Do NOT cross here or you'll gradually empty the buffer! + sequence([a,b]) + end + end + + # Setting conservative=true would mess with the delayed switch below + cross(duration=cross_d,conservative=false,crosser,source) + +end + +# Custom fallback between http and default source with fading of +# beginning and end of HTTP stream. +# It does not take potential URL changes into account, as long as +# they do not interrupt streaming (thanks to the HTTP buffer). +def http_fallback(~http_input_id,~http,~default) + + id = http_input_id + + # We use a custom switching predicate to trigger switching (and thus, + # transitions) before the end of a track (rather, end of HTTP stream). + # It is complexified because we don't want to trigger switching when + # HTTP disconnects for just an instant, when changing URL: for that + # we use gracetime below. + + def gracetime(~delay=3.,f) + last_true = ref 0. + { if f() then + last_true := gettimeofday() + true + else + gettimeofday() < !last_true+delay + end } + end + + def connected() + status = list.hd(server.execute("#{id}.status")) + not(list.mem(status,["polling","stopped"])) + end + connected = gracetime(connected) + + def to_live(a,b) = + log("TRANSITION to live") + add(normalize=false, + [fade.initial(b),fade.final(a)]) + end + def to_static(a,b) = + log("TRANSITION to static") + sequence([fade.out(a),fade.initial(b)]) + end + + switch( + track_sensitive=false, + transitions=[to_live,to_static], + [(# make sure it is connected, and not buffering + {connected() and source.is_ready(http) and !webstream_enabled}, http), + ({true},default)]) + +end diff --git a/python_apps/pypo/liquidsoap/ls_script.liq b/python_apps/pypo/liquidsoap/ls_script.liq index 14fd0f323..a832a0e1f 100644 --- a/python_apps/pypo/liquidsoap/ls_script.liq +++ b/python_apps/pypo/liquidsoap/ls_script.liq @@ -41,7 +41,7 @@ source_id = ref 0 def check_version(~version=liquidsoap.version, major, minor) = v = list.map(int_of_string, string.split(separator="\.", version)) - list.nth(v,0) > major or list.nth(v,0) == major and list.nth(v,1) >= minor + list.nth(v,0,default=0) > major or list.nth(v,0,default=0) == major and list.nth(v,1,default=0) >= minor end # cue cut fix for liquidsoap <1.2.2 @@ -235,26 +235,40 @@ def master_dj_disconnect() = update_source_status("master_dj", false) end -#auth function for live stream -def check_master_dj_client(user,password) = - log("master connected") - #get the output of the php script - ret = get_process_lines("python #{auth_path} --master #{user} #{password}") - #ret has now the value of the live client (dj1,dj2, or djx), or "ERROR"/"unknown" ... - ret = list.hd(ret) +# Auth function for live stream +# @Category LiveStream +# @param user Username to check against LibreTime API +# @param password Password to check against LibreTime API +# @param ~type Type of password to check, "dj" or "master, default: "master" +def check_auth(user="", password="", ~type="master") = + log("#{type} user #{user} connected",label="#{type}_source") - #return true to let the client transmit data, or false to tell harbor to decline - ret == "True" + # Check auth based on return value from auth script + ret = snd(snd(run_process("python #{auth_path} --#{type} #{user} #{password}"))) == "0" + + if ret then + log("#{type} user #{user} authenticated",label="#{type}_source") + else + log("#{type} user #{user} auth failed",label="#{type}_source",level=2) + end + + ret end -def check_dj_client(user,password) = - log("live dj connected") - #get the output of the php script - ret = get_process_lines("python #{auth_path} --dj #{user} #{password}") - #ret has now the value of the live client (dj1,dj2, or djx), or "ERROR"/"unknown" ... - hd = list.hd(ret) - log("Live DJ authenticated: #{hd}") - hd == "True" +# Check master source auth +# @Category LiveStream +# @param user Username to check against LibreTime API +# @param password Password to check against LibreTime API +def check_master_dj_client(user, password) = + check_auth(user, password) +end + +# Check dj/show source auth +# @Category LiveStream +# @param user Username to check against LibreTime API +# @param password Password to check against LibreTime API +def check_dj_client(user, password) = + check_auth(user, password, type="dj") end s = switch(id="schedule_noise_switch", diff --git a/python_apps/pypo/liquidsoap/ls_script_legacy.liq b/python_apps/pypo/liquidsoap/ls_script_legacy.liq new file mode 100644 index 000000000..c4a8b99af --- /dev/null +++ b/python_apps/pypo/liquidsoap/ls_script_legacy.liq @@ -0,0 +1,445 @@ +%include "/etc/airtime/liquidsoap.cfg" + +set("log.file.path", log_file) +set("server.telnet", true) +set("server.telnet.port", 1234) +# set("init.daemon.pidfile.path", "/var/run/airtime/airtime-liquidsoap.pid") + + +#Dynamic source list +#dyn_sources = ref [] +webstream_enabled = ref false + +time = ref string_of(gettimeofday()) + +#live stream setup +set("harbor.bind_addr", "0.0.0.0") + +current_dyn_id = ref '-1' + +pypo_data = ref '0' +stream_metadata_type = ref 0 +default_dj_fade = ref 0. +station_name = ref '' +show_name = ref '' + +dynamic_metadata_callback = ref fun (s) -> begin () end + +s1_connected = ref '' +s2_connected = ref '' +s3_connected = ref '' +s4_connected = ref '' +s1_namespace = ref '' +s2_namespace = ref '' +s3_namespace = ref '' +just_switched = ref false + +%include "ls_lib_legacy.liq" + +sources = ref [] +source_id = ref 0 + +def check_version(~version=liquidsoap.version, major, minor) = + v = list.map(int_of_string, string.split(separator="\.", version)) + list.nth(v,0) > major or list.nth(v,0) == major and list.nth(v,1) >= minor +end + +# cue cut fix for liquidsoap <1.2.2 +# +# This was most likely broken on 1.1.1 (debian) as well. +# +# adapted from https://github.com/savonet/liquidsoap/issues/390#issuecomment-277562081 +# +def fix_cue_in(~cue_in_metadata='liq_cue_in', m) = + # 0.04 might need to be adjusted according to your frame size + if float_of_string(m[cue_in_metadata]) < 0.04 then + [(cue_in_metadata, "0")] + else + [] + end +end + +def create_source() + l = request.equeue(id="s#{!source_id}", length=0.5) + + l = audio_to_stereo(id="queue_src", l) + + l = if not check_version(1, 3) then + map_metadata(fix_cue_in, l) + else + l + end + l = cue_cut(l) + l = amplify(1., override="replay_gain", l) + + # the crossfade function controls fade in/out + l = crossfade_airtime(l) + + l = on_metadata(notify_queue, l) + + sources := list.append([l], !sources) + server.register(namespace="queues", + "s#{!source_id}_skip", + fun (s) -> begin log("queues.s#{!source_id}_skip") + clear_queue(l) + "Done" + end) + source_id := !source_id + 1 +end + +create_source() +create_source() +create_source() +create_source() + +create_source() +create_source() +create_source() +create_source() + +queue = add(!sources, normalize=false) +pair = insert_metadata(queue) +dynamic_metadata_callback := fst(pair) +queue = snd(pair) + +output.dummy(fallible=true, queue) + +http = input.http_restart(id="http") +http = cross_http(http_input_id="http",http) +output.dummy(fallible=true, http) +stream_queue = http_fallback(http_input_id="http", http=http, default=queue) +stream_queue = map_metadata(update=false, append_title, stream_queue) + +ignore(output.dummy(stream_queue, fallible=true)) + +server.register(namespace="vars", + "pypo_data", + fun (s) -> begin log("vars.pypo_data") pypo_data := s "Done" end) +server.register(namespace="vars", + "stream_metadata_type", + fun (s) -> begin log("vars.stream_metadata_type") stream_metadata_type := int_of_string(s) s end) +server.register(namespace="vars", + "show_name", + fun (s) -> begin log("vars.show_name") show_name := s s end) +server.register(namespace="vars", + "station_name", + fun (s) -> begin log("vars.station_name") station_name := s s end) +server.register(namespace="vars", + "bootup_time", + fun (s) -> begin log("vars.bootup_time") time := s s end) +server.register(namespace="streams", + "connection_status", + fun (s) -> begin log("streams.connection_status") "1:#{!s1_connected},2:#{!s2_connected},3:#{!s3_connected},4:#{!s4_connected}" end) +server.register(namespace="vars", + "default_dj_fade", + fun (s) -> begin log("vars.default_dj_fade") default_dj_fade := float_of_string(s) s end) + +server.register(namespace="dynamic_source", + description="Enable webstream output", + usage='start', + "output_start", + fun (s) -> begin log("dynamic_source.output_start") + notify([("schedule_table_id", !current_dyn_id)]) + webstream_enabled := true "enabled" end) +server.register(namespace="dynamic_source", + description="Enable webstream output", + usage='stop', + "output_stop", + fun (s) -> begin log("dynamic_source.output_stop") webstream_enabled := false "disabled" end) + +server.register(namespace="dynamic_source", + description="Set the streams cc_schedule row id", + usage="id ", + "id", + fun (s) -> begin log("dynamic_source.id") set_dynamic_source_id(s) end) + +server.register(namespace="dynamic_source", + description="Get the streams cc_schedule row id", + usage="get_id", + "get_id", + fun (s) -> begin log("dynamic_source.get_id") get_dynamic_source_id() end) + +#server.register(namespace="dynamic_source", +# description="Start a new dynamic source.", +# usage="start ", +# "read_start", +# fun (uri) -> begin log("dynamic_source.read_start") begin_stream_read(uri) end) +#server.register(namespace="dynamic_source", +# description="Stop a dynamic source.", +# usage="stop ", +# "read_stop", +# fun (s) -> begin log("dynamic_source.read_stop") stop_stream_read(s) end) + +#server.register(namespace="dynamic_source", +# description="Stop a dynamic source.", +# usage="stop ", +# "read_stop_all", +# fun (s) -> begin log("dynamic_source.read_stop") destroy_dynamic_source_all() end) + +default = amplify(id="silence_src", 0.00001, noise()) +ref_off_air_meta = ref off_air_meta +if !ref_off_air_meta == "" then + ref_off_air_meta := "LibreTime - offline" +end +default = rewrite_metadata([("title", !ref_off_air_meta)], default) +ignore(output.dummy(default, fallible=true)) + +master_dj_enabled = ref false +live_dj_enabled = ref false +scheduled_play_enabled = ref false + +def make_master_dj_available() + master_dj_enabled := true +end + +def make_master_dj_unavailable() + master_dj_enabled := false +end + +def make_live_dj_available() + live_dj_enabled := true +end + +def make_live_dj_unavailable() + live_dj_enabled := false +end + +def make_scheduled_play_available() + scheduled_play_enabled := true + just_switched := true +end + +def make_scheduled_play_unavailable() + scheduled_play_enabled := false +end + +def update_source_status(sourcename, status) = + command = "timeout --signal=KILL 45 pyponotify --source-name=#{sourcename} --source-status=#{status} &" + system(command) + log(command) +end + +def live_dj_connect(header) = + update_source_status("live_dj", true) +end + +def live_dj_disconnect() = + update_source_status("live_dj", false) +end + +def master_dj_connect(header) = + update_source_status("master_dj", true) +end + +def master_dj_disconnect() = + update_source_status("master_dj", false) +end + +#auth function for live stream +def check_master_dj_client(user,password) = + log("master connected") + #get the output of the php script + ret = get_process_lines("python #{auth_path} --master #{user} #{password}") + #ret has now the value of the live client (dj1,dj2, or djx), or "ERROR"/"unknown" ... + ret = list.hd(ret) + + #return true to let the client transmit data, or false to tell harbor to decline + ret == "True" +end + +def check_dj_client(user,password) = + log("live dj connected") + #get the output of the php script + ret = get_process_lines("python #{auth_path} --dj #{user} #{password}") + #ret has now the value of the live client (dj1,dj2, or djx), or "ERROR"/"unknown" ... + hd = list.hd(ret) + log("Live DJ authenticated: #{hd}") + hd == "True" +end + +s = switch(id="schedule_noise_switch", + track_sensitive=false, + transitions=[transition_default, transition], + [({!scheduled_play_enabled}, stream_queue), ({true}, default)] + ) + +s = if dj_live_stream_port != 0 and dj_live_stream_mp != "" then + dj_live = + audio_to_stereo( + input.harbor(id="live_dj_harbor", + dj_live_stream_mp, + port=dj_live_stream_port, + auth=check_dj_client, + max=40., + on_connect=live_dj_connect, + on_disconnect=live_dj_disconnect)) + + ignore(output.dummy(dj_live, fallible=true)) + + switch(id="show_schedule_noise_switch", + track_sensitive=false, + transitions=[transition, transition], + [({!live_dj_enabled}, dj_live), ({true}, s)] + ) +else + s +end + +s = if master_live_stream_port != 0 and master_live_stream_mp != "" then + master_dj = + audio_to_stereo( + input.harbor(id="master_harbor", + master_live_stream_mp, + port=master_live_stream_port, + auth=check_master_dj_client, + max=40., + on_connect=master_dj_connect, + on_disconnect=master_dj_disconnect)) + + ignore(output.dummy(master_dj, fallible=true)) + + switch(id="master_show_schedule_noise_switch", + track_sensitive=false, + transitions=[transition, transition], + [({!master_dj_enabled}, master_dj), ({true}, s)] + ) +else + s +end + + +# Attach a skip command to the source s: +#add_skip_command(s) + +server.register(namespace="streams", + description="Stop Master DJ source.", + usage="master_dj_stop", + "master_dj_stop", + fun (s) -> begin log("streams.master_dj_stop") make_master_dj_unavailable() "Done." end) +server.register(namespace="streams", + description="Start Master DJ source.", + usage="master_dj_start", + "master_dj_start", + fun (s) -> begin log("streams.master_dj_start") make_master_dj_available() "Done." end) +server.register(namespace="streams", + description="Stop Live DJ source.", + usage="live_dj_stop", + "live_dj_stop", + fun (s) -> begin log("streams.live_dj_stop") make_live_dj_unavailable() "Done." end) +server.register(namespace="streams", + description="Start Live DJ source.", + usage="live_dj_start", + "live_dj_start", + fun (s) -> begin log("streams.live_dj_start") make_live_dj_available() "Done." end) +server.register(namespace="streams", + description="Stop Scheduled Play source.", + usage="scheduled_play_stop", + "scheduled_play_stop", + fun (s) -> begin log("streams.scheduled_play_stop") make_scheduled_play_unavailable() "Done." end) +server.register(namespace="streams", + description="Start Scheduled Play source.", + usage="scheduled_play_start", + "scheduled_play_start", + fun (s) -> begin log("streams.scheduled_play_start") make_scheduled_play_available() "Done." end) + +if output_sound_device then + success = ref false + + log(output_sound_device_type) + + %ifdef output.alsa + if output_sound_device_type == "ALSA" then + ignore(output.alsa(s)) + success := true + end + %endif + + %ifdef output.ao + if output_sound_device_type == "AO" then + ignore(output.ao(s)) + success := true + end + %endif + + %ifdef output.oss + if output_sound_device_type == "OSS" then + ignore(output.oss(s)) + success := true + end + %endif + + %ifdef output.portaudio + if output_sound_device_type == "Portaudio" then + ignore(output.portaudio(s)) + success := true + end + %endif + + %ifdef output.pulseaudio + if output_sound_device_type == "Pulseaudio" then + ignore(output.pulseaudio(s)) + success := true + end + %endif + + if (!success == false) then + ignore(output.prefered(s)) + end + +end + +if s1_enable == true then + if s1_output == 'shoutcast' then + s1_namespace := "shoutcast_stream_1" + else + s1_namespace := s1_mount + end + server.register(namespace=!s1_namespace, "connected", fun (s) -> begin log("#{!s1_namespace}.connected") !s1_connected end) + output_to(s1_output, s1_type, s1_bitrate, s1_host, s1_port, s1_pass, + s1_mount, s1_url, s1_description, s1_genre, s1_user, s, "1", + s1_connected, s1_name, s1_channels) +end + +if s2_enable == true then + if s2_output == 'shoutcast' then + s2_namespace := "shoutcast_stream_2" + else + s2_namespace := s2_mount + end + server.register(namespace=!s2_namespace, "connected", fun (s) -> begin log("#{!s2_namespace}.connected") !s2_connected end) + output_to(s2_output, s2_type, s2_bitrate, s2_host, s2_port, s2_pass, + s2_mount, s2_url, s2_description, s2_genre, s2_user, s, "2", + s2_connected, s2_name, s2_channels) + +end + +if s3_enable == true then + if s3_output == 'shoutcast' then + s3_namespace := "shoutcast_stream_3" + else + s3_namespace := s3_mount + end + server.register(namespace=!s3_namespace, "connected", fun (s) -> begin log("#{!s3_namespace}.connected") !s3_connected end) + output_to(s3_output, s3_type, s3_bitrate, s3_host, s3_port, s3_pass, + s3_mount, s3_url, s3_description, s3_genre, s3_user, s, "3", + s3_connected, s3_name, s3_channels) +end + +s4_namespace = ref '' +if s4_enable == true then + log("Stream 4 Enabled") + if s4_output == 'shoutcast' then + s4_namespace := "shoutcast_stream_4" + else + s4_namespace := s4_mount + end + server.register(namespace=!s4_namespace, "connected", fun (s) -> begin log("#{!s4_namespace}.connected") !s4_connected end) + output_to(s4_output, s4_type, s4_bitrate, s4_host, s4_port, s4_pass, + s4_mount, s4_url, s4_name, s4_genre, s4_user, s, "4", + s4_connected, s4_description, s4_channels) +end + + +command = "timeout --signal=KILL 45 pyponotify --liquidsoap-started &" +log(command) +system(command) From 5d3295c86c853e99a1c76820ce9ff0fd081ee8c4 Mon Sep 17 00:00:00 2001 From: Robb Ebright Date: Wed, 1 Jan 2020 21:03:30 -0500 Subject: [PATCH 06/23] first stab at icecast password change on install --- airtime_mvc/public/setup/database-setup.php | 21 +++++++++++++++++++ install | 9 ++++++++ installer/lib/requirements-debian-buster.apt | 2 ++ installer/lib/requirements-debian-jessie.apt | 2 ++ installer/lib/requirements-debian-stretch.apt | 2 ++ installer/lib/requirements-ubuntu-bionic.apt | 2 ++ installer/lib/requirements-ubuntu-precise.apt | 2 ++ installer/lib/requirements-ubuntu-xenial.apt | 2 ++ ...uirements-ubuntu-xenial_docker_minimal.apt | 2 ++ 9 files changed, 44 insertions(+) diff --git a/airtime_mvc/public/setup/database-setup.php b/airtime_mvc/public/setup/database-setup.php index 17038ac74..bbbecd9fc 100644 --- a/airtime_mvc/public/setup/database-setup.php +++ b/airtime_mvc/public/setup/database-setup.php @@ -79,6 +79,7 @@ class DatabaseSetup extends Setup { $this->setNewDatabaseConnection(self::$_properties["dbname"]); $this->checkSchemaExists(); $this->createDatabaseTables(); + $this->updateIcecastPassword(); } /** @@ -175,5 +176,25 @@ class DatabaseSetup extends Setup { array(self::DB_NAME,)); } } + /** + * Updates the icecast password in the database based upon the temp file created during install + * @throws AirtimeDatabaseException + */ + private function updateIcecastPassword() { + if (!file_exists(LIBRETIME_CONF_DIR . '/icecast_pass')) { + throw new AirtimeDatabaseException("The Icecast Password file was not accessible", array()); + }; + $icecastPass = file_get_contents(LIBRETIME_CONF_DIR . '/icecast_pass', true); + error_log($icecastPass); + $statement = self::$dbh->prepare("UPDATE cc_stream_setting SET value = :icecastpass WHERE keyname = 's1_pass' AND SET value = :icecastpass WHERE keyname = 's1_admin_pass'; " + . "UPDATE cc_stream_setting SET value = :icecastpass WHERE keyname = 's2_pass'; " + . "UPDATE cc_stream_setting SET value = :icecastpass WHERE keyname = 's2_admin_pass'; " + . "UPDATE cc_stream_setting SET value = :icecastpass WHERE keyname = 's3_pass'; " + . "UPDATE cc_stream_setting SET value = :icecastpass WHERE keyname = 's3_admin_pass'; " + . "INSERT INTO cc_pref (keystr, valstr) VALUES ('default_icecast_password', :icecastpass )"); + if (!$statement->execute(array(":icecastpass" => $icecastPass))) { + throw new AirtimeDatabaseException("Could not update the database with icecast password!", array()); + } + } } diff --git a/install b/install index 1dcd695b5..b1b8a1f42 100755 --- a/install +++ b/install @@ -894,6 +894,12 @@ if [ "$icecast" = "t" ]; then else icecast_unit_name="icecast" fi + icecast_pass=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-12};echo;) + echo $icecast_pass > /tmp/icecast_pass + xmlstarlet ed --inplace -u /icecast/authentication/source-password -v $icecast_pass /etc/icecast2/icecast.xml + xmlstarlet ed --inplace -u /icecast/authentication/relay-password -v $icecast_pass /etc/icecast2/icecast.xml + xmlstarlet ed --inplace -u /icecast/authentication/admin-password -v $icecast_pass /etc/icecast2/icecast.xml + # restart in case icecast was already started (like is the case on debian) systemInitCommand restart ${icecast_unit_name} verbose "...Done" @@ -1100,6 +1106,9 @@ if [ ! -d "/etc/airtime" ]; then mkdir /etc/airtime fi +# need to copy the icecast_pass from temp to /etc/airtime so installer can read it + cp /tmp/icecast_pass /etc/airtime/icecast_pass + chown -R ${web_user}:${web_user} /etc/airtime diff --git a/installer/lib/requirements-debian-buster.apt b/installer/lib/requirements-debian-buster.apt index e50bd7aed..5c665ec07 100644 --- a/installer/lib/requirements-debian-buster.apt +++ b/installer/lib/requirements-debian-buster.apt @@ -67,3 +67,5 @@ liquidsoap libopus0 systemd-sysv + +xmlstarlet diff --git a/installer/lib/requirements-debian-jessie.apt b/installer/lib/requirements-debian-jessie.apt index 548835c90..4e22102cc 100644 --- a/installer/lib/requirements-debian-jessie.apt +++ b/installer/lib/requirements-debian-jessie.apt @@ -63,3 +63,5 @@ libopus0 sysvinit sysvinit-utils + +xmlstarlet diff --git a/installer/lib/requirements-debian-stretch.apt b/installer/lib/requirements-debian-stretch.apt index 5f11226cb..c71175f6a 100644 --- a/installer/lib/requirements-debian-stretch.apt +++ b/installer/lib/requirements-debian-stretch.apt @@ -67,3 +67,5 @@ liquidsoap libopus0 systemd-sysv + +xmlstarlet diff --git a/installer/lib/requirements-ubuntu-bionic.apt b/installer/lib/requirements-ubuntu-bionic.apt index 08e6f21a6..58be7819f 100644 --- a/installer/lib/requirements-ubuntu-bionic.apt +++ b/installer/lib/requirements-ubuntu-bionic.apt @@ -81,3 +81,5 @@ build-essential libssl-dev libffi-dev python-dev + +xmlstarlet diff --git a/installer/lib/requirements-ubuntu-precise.apt b/installer/lib/requirements-ubuntu-precise.apt index 7c217f659..ed31b628f 100644 --- a/installer/lib/requirements-ubuntu-precise.apt +++ b/installer/lib/requirements-ubuntu-precise.apt @@ -70,3 +70,5 @@ liquidsoap-plugin-pulseaudio liquidsoap-plugin-taglib liquidsoap-plugin-voaacenc liquidsoap-plugin-vorbis + +xmlstarlet diff --git a/installer/lib/requirements-ubuntu-xenial.apt b/installer/lib/requirements-ubuntu-xenial.apt index 70336c10c..41381915f 100644 --- a/installer/lib/requirements-ubuntu-xenial.apt +++ b/installer/lib/requirements-ubuntu-xenial.apt @@ -81,3 +81,5 @@ build-essential libssl-dev libffi-dev python-dev + +xmlstarlet diff --git a/installer/lib/requirements-ubuntu-xenial_docker_minimal.apt b/installer/lib/requirements-ubuntu-xenial_docker_minimal.apt index 6d414ba2d..09c94f817 100644 --- a/installer/lib/requirements-ubuntu-xenial_docker_minimal.apt +++ b/installer/lib/requirements-ubuntu-xenial_docker_minimal.apt @@ -76,3 +76,5 @@ build-essential libssl-dev libffi-dev python-dev + +xmlstarlet From 7c9ca660b323d425d99651f43c87ca285d0a8237 Mon Sep 17 00:00:00 2001 From: Kyle Robbertze Date: Thu, 2 Jan 2020 11:10:57 +0200 Subject: [PATCH 07/23] fix broken link in install doc --- docs/install.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/install.md b/docs/install.md index 201334b6d..c0c3e5c1d 100644 --- a/docs/install.md +++ b/docs/install.md @@ -45,5 +45,5 @@ Plans are in the works for `.rpm` packages, as well as Docker and AWS images. Please note that the install script does not take care to ensure that any packages installed are set up in a secure manner. Please see the chapter on -[preparing the server](manual/preparing-the-server.md) for more details on +[preparing the server](manual/preparing-the-server) for more details on how to set up a secure installation. From 88a7cf6a3e7ca4be026e68947c9dea0fd8e3569d Mon Sep 17 00:00:00 2001 From: Robbt Date: Fri, 3 Jan 2020 13:20:40 -0500 Subject: [PATCH 08/23] fix database statements --- airtime_mvc/public/setup/database-setup.php | 86 ++++++++++++++++++--- 1 file changed, 75 insertions(+), 11 deletions(-) diff --git a/airtime_mvc/public/setup/database-setup.php b/airtime_mvc/public/setup/database-setup.php index bbbecd9fc..4ff510713 100644 --- a/airtime_mvc/public/setup/database-setup.php +++ b/airtime_mvc/public/setup/database-setup.php @@ -184,17 +184,81 @@ class DatabaseSetup extends Setup { if (!file_exists(LIBRETIME_CONF_DIR . '/icecast_pass')) { throw new AirtimeDatabaseException("The Icecast Password file was not accessible", array()); }; - $icecastPass = file_get_contents(LIBRETIME_CONF_DIR . '/icecast_pass', true); - error_log($icecastPass); - $statement = self::$dbh->prepare("UPDATE cc_stream_setting SET value = :icecastpass WHERE keyname = 's1_pass' AND SET value = :icecastpass WHERE keyname = 's1_admin_pass'; " - . "UPDATE cc_stream_setting SET value = :icecastpass WHERE keyname = 's2_pass'; " - . "UPDATE cc_stream_setting SET value = :icecastpass WHERE keyname = 's2_admin_pass'; " - . "UPDATE cc_stream_setting SET value = :icecastpass WHERE keyname = 's3_pass'; " - . "UPDATE cc_stream_setting SET value = :icecastpass WHERE keyname = 's3_admin_pass'; " - . "INSERT INTO cc_pref (keystr, valstr) VALUES ('default_icecast_password', :icecastpass )"); - if (!$statement->execute(array(":icecastpass" => $icecastPass))) { - throw new AirtimeDatabaseException("Could not update the database with icecast password!", array()); - } + $icecast_pass = file_get_contents(LIBRETIME_CONF_DIR . '/icecast_pass', true); + error_log($icecast_pass); + $statement = self::$dbh->prepare("UPDATE cc_stream_setting SET value = :icecastpass WHERE keyname = 's1_pass'"); + $statement->bindValue(':icecastpass', $icecast_pass, PDO::PARAM_STR); + try { + $statement->execute(); + } + catch (PDOException $ex) { + print "Error!: " . $ex->getMessage() . "
"; + die(); + } + $statement = self::$dbh->prepare("UPDATE cc_stream_setting SET value = :icecastpass WHERE keyname = 's1_admin_pass'"); + $statement->bindValue(':icecastpass', $icecast_pass, PDO::PARAM_STR); + try { + $statement->execute(); + } + catch (PDOException $ex) { + print "Error!: " . $ex->getMessage() . "
"; + die(); + } + $statement = self::$dbh->prepare("UPDATE cc_stream_setting SET value = :icecastpass WHERE keyname = 's2_pass'"); + $statement->bindValue(':icecastpass', $icecast_pass, PDO::PARAM_STR); + try { + $statement->execute(); + } + catch (PDOException $ex) { + print "Error!: " . $ex->getMessage() . "
"; + die(); + } + $statement = self::$dbh->prepare("UPDATE cc_stream_setting SET value = :icecastpass WHERE keyname = 's2_admin_pass'"); + $statement->bindValue(':icecastpass', $icecast_pass, PDO::PARAM_STR); + try { + $statement->execute(); + } + catch (PDOException $ex) { + print "Error!: " . $ex->getMessage() . "
"; + die(); + } + + $statement = self::$dbh->prepare("UPDATE cc_stream_setting SET value = :icecastpass WHERE keyname = 's3_pass'"); + $statement->bindValue(':icecastpass', $icecast_pass, PDO::PARAM_STR); + try { + $statement->execute(); + } + catch (PDOException $ex) { + print "Error!: " . $ex->getMessage() . "
"; + die(); + } + $statement = self::$dbh->prepare("UPDATE cc_stream_setting SET value = :icecastpass WHERE keyname = 's3_admin_pass'"); + $statement->bindValue(':icecastpass', $icecast_pass, PDO::PARAM_STR); + try { + $statement->execute(); + } + catch (PDOException $ex) { + print "Error!: " . $ex->getMessage() . "
"; + die(); + } + $statement = self::$dbh->prepare("UPDATE cc_stream_setting SET value = :icecastpass WHERE keyname = 's1_admin_pass'"); + $statement->bindValue(':icecastpass', $icecast_pass, PDO::PARAM_STR); + try { + $statement->execute(); + } + catch (PDOException $ex) { + print "Error!: " . $ex->getMessage() . "
"; + die(); + } + $statement = self::$dbh->prepare("INSERT INTO cc_pref (keystr, valstr) VALUES ('default_icecast_password', :icecastpass )"); + $statement->bindValue(':icecastpass', $icecast_pass, PDO::PARAM_STR); + try { + $statement->execute(); + } + catch (PDOException $ex) { + print "Error!: " . $ex->getMessage() . "
"; + die(); + } } } From 9a027373e19af13ac242166ff8f34471c867ca14 Mon Sep 17 00:00:00 2001 From: Robbt Date: Fri, 3 Jan 2020 15:00:27 -0500 Subject: [PATCH 09/23] fixed database page stall --- airtime_mvc/public/setup/database-setup.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/airtime_mvc/public/setup/database-setup.php b/airtime_mvc/public/setup/database-setup.php index 4ff510713..87f5c839c 100644 --- a/airtime_mvc/public/setup/database-setup.php +++ b/airtime_mvc/public/setup/database-setup.php @@ -185,7 +185,6 @@ class DatabaseSetup extends Setup { throw new AirtimeDatabaseException("The Icecast Password file was not accessible", array()); }; $icecast_pass = file_get_contents(LIBRETIME_CONF_DIR . '/icecast_pass', true); - error_log($icecast_pass); $statement = self::$dbh->prepare("UPDATE cc_stream_setting SET value = :icecastpass WHERE keyname = 's1_pass'"); $statement->bindValue(':icecastpass', $icecast_pass, PDO::PARAM_STR); try { @@ -193,7 +192,6 @@ class DatabaseSetup extends Setup { } catch (PDOException $ex) { print "Error!: " . $ex->getMessage() . "
"; - die(); } $statement = self::$dbh->prepare("UPDATE cc_stream_setting SET value = :icecastpass WHERE keyname = 's1_admin_pass'"); $statement->bindValue(':icecastpass', $icecast_pass, PDO::PARAM_STR); @@ -202,7 +200,6 @@ class DatabaseSetup extends Setup { } catch (PDOException $ex) { print "Error!: " . $ex->getMessage() . "
"; - die(); } $statement = self::$dbh->prepare("UPDATE cc_stream_setting SET value = :icecastpass WHERE keyname = 's2_pass'"); $statement->bindValue(':icecastpass', $icecast_pass, PDO::PARAM_STR); @@ -211,7 +208,6 @@ class DatabaseSetup extends Setup { } catch (PDOException $ex) { print "Error!: " . $ex->getMessage() . "
"; - die(); } $statement = self::$dbh->prepare("UPDATE cc_stream_setting SET value = :icecastpass WHERE keyname = 's2_admin_pass'"); $statement->bindValue(':icecastpass', $icecast_pass, PDO::PARAM_STR); @@ -220,7 +216,6 @@ class DatabaseSetup extends Setup { } catch (PDOException $ex) { print "Error!: " . $ex->getMessage() . "
"; - die(); } $statement = self::$dbh->prepare("UPDATE cc_stream_setting SET value = :icecastpass WHERE keyname = 's3_pass'"); @@ -230,7 +225,6 @@ class DatabaseSetup extends Setup { } catch (PDOException $ex) { print "Error!: " . $ex->getMessage() . "
"; - die(); } $statement = self::$dbh->prepare("UPDATE cc_stream_setting SET value = :icecastpass WHERE keyname = 's3_admin_pass'"); $statement->bindValue(':icecastpass', $icecast_pass, PDO::PARAM_STR); @@ -239,7 +233,6 @@ class DatabaseSetup extends Setup { } catch (PDOException $ex) { print "Error!: " . $ex->getMessage() . "
"; - die(); } $statement = self::$dbh->prepare("UPDATE cc_stream_setting SET value = :icecastpass WHERE keyname = 's1_admin_pass'"); $statement->bindValue(':icecastpass', $icecast_pass, PDO::PARAM_STR); @@ -248,7 +241,6 @@ class DatabaseSetup extends Setup { } catch (PDOException $ex) { print "Error!: " . $ex->getMessage() . "
"; - die(); } $statement = self::$dbh->prepare("INSERT INTO cc_pref (keystr, valstr) VALUES ('default_icecast_password', :icecastpass )"); $statement->bindValue(':icecastpass', $icecast_pass, PDO::PARAM_STR); @@ -257,7 +249,6 @@ class DatabaseSetup extends Setup { } catch (PDOException $ex) { print "Error!: " . $ex->getMessage() . "
"; - die(); } } From edaa2ead85ffcc46c7651382934e3ea327f75ecd Mon Sep 17 00:00:00 2001 From: Robbt Date: Fri, 3 Jan 2020 20:14:02 -0500 Subject: [PATCH 10/23] fixed new line in php --- airtime_mvc/public/setup/database-setup.php | 5 ++++- install | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/airtime_mvc/public/setup/database-setup.php b/airtime_mvc/public/setup/database-setup.php index 87f5c839c..1fa894255 100644 --- a/airtime_mvc/public/setup/database-setup.php +++ b/airtime_mvc/public/setup/database-setup.php @@ -184,7 +184,10 @@ class DatabaseSetup extends Setup { if (!file_exists(LIBRETIME_CONF_DIR . '/icecast_pass')) { throw new AirtimeDatabaseException("The Icecast Password file was not accessible", array()); }; - $icecast_pass = file_get_contents(LIBRETIME_CONF_DIR . '/icecast_pass', true); + $icecast_pass_txt = file(LIBRETIME_CONF_DIR . '/icecast_pass'); + $icecast_pass = $icecast_pass_txt[0]; + $icecast_pass = str_replace(PHP_EOL, '', $icecast_pass); + error_log($icecast_pass); $statement = self::$dbh->prepare("UPDATE cc_stream_setting SET value = :icecastpass WHERE keyname = 's1_pass'"); $statement->bindValue(':icecastpass', $icecast_pass, PDO::PARAM_STR); try { diff --git a/install b/install index b1b8a1f42..897adae30 100755 --- a/install +++ b/install @@ -894,7 +894,7 @@ if [ "$icecast" = "t" ]; then else icecast_unit_name="icecast" fi - icecast_pass=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-12};echo;) + icecast_pass=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-12};) echo $icecast_pass > /tmp/icecast_pass xmlstarlet ed --inplace -u /icecast/authentication/source-password -v $icecast_pass /etc/icecast2/icecast.xml xmlstarlet ed --inplace -u /icecast/authentication/relay-password -v $icecast_pass /etc/icecast2/icecast.xml From 54ad27ab755aad9b60d060f02ead912d136ba722 Mon Sep 17 00:00:00 2001 From: Kyle Robbertze Date: Mon, 6 Jan 2020 12:43:53 +0200 Subject: [PATCH 11/23] List install first in docs The install processes is the first thing users need to do, thus list it first --- mkdocs.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index d744f54a4..55c686d48 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -20,6 +20,10 @@ pages: - 'Features': features.md - 'F.A.Q.': faq.md - 'Rights and Royalties': manual/rights-and-royalties/index.md + - 'Installation': + - 'Install': install.md + - 'Preparing the Server': manual/preparing-the-server/index.md + - 'Setting the Server Time': manual/setting-the-server-time/index.md - 'Using LibreTime': - 'On Air in 60 seconds!': 'manual/on-air-in-60-seconds/index.md' - 'Getting Started': manual/getting-started/index.md @@ -55,10 +59,6 @@ pages: - 'Smartphone Journalism': manual/smartphone-journalism/index.md - 'Icecast and SHOUTcast': manual/icecast-and-shoutcast/index.md - 'Recording Shows': manual/recording-shows/index.md - - 'Installation': - - 'Install': install.md - - 'Preparing the Server': manual/preparing-the-server/index.md - - 'Setting the Server Time': manual/setting-the-server-time/index.md - 'Administration': - 'Backing Up the Server': manual/backing-up-the-server/index.md - 'Media Folders': manual/media-folders/index.md From 211ce99a2cd431241c9edacf6a13849b49e1ca39 Mon Sep 17 00:00:00 2001 From: Robb Ebright Date: Mon, 6 Jan 2020 13:58:31 -0500 Subject: [PATCH 12/23] removed debugging log --- airtime_mvc/public/setup/database-setup.php | 1 - 1 file changed, 1 deletion(-) diff --git a/airtime_mvc/public/setup/database-setup.php b/airtime_mvc/public/setup/database-setup.php index 1fa894255..a3e3ff2b6 100644 --- a/airtime_mvc/public/setup/database-setup.php +++ b/airtime_mvc/public/setup/database-setup.php @@ -187,7 +187,6 @@ class DatabaseSetup extends Setup { $icecast_pass_txt = file(LIBRETIME_CONF_DIR . '/icecast_pass'); $icecast_pass = $icecast_pass_txt[0]; $icecast_pass = str_replace(PHP_EOL, '', $icecast_pass); - error_log($icecast_pass); $statement = self::$dbh->prepare("UPDATE cc_stream_setting SET value = :icecastpass WHERE keyname = 's1_pass'"); $statement->bindValue(':icecastpass', $icecast_pass, PDO::PARAM_STR); try { From 5469b0874c178b6ce6e6f4259251eb45c05c5da5 Mon Sep 17 00:00:00 2001 From: Kyle Robbertze Date: Tue, 7 Jan 2020 09:36:03 +0200 Subject: [PATCH 13/23] Document CORS configuration better Fixes #917 --- docs/manual/general/index.md | 4 +++- docs/manual/getting-started/index.md | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/docs/manual/general/index.md b/docs/manual/general/index.md index d30c706af..c4dca1e11 100644 --- a/docs/manual/general/index.md +++ b/docs/manual/general/index.md @@ -50,7 +50,9 @@ wish. (There is more about this feature 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. +remote site with a different domain to access the API. This is relevant when +there is a reverse proxy server in front of LibreTime. If you are using a +reverse proxy, the URLs that will be used to access it should be added here. The **Display login button on your Radio Page?** will determine whether visitors to your site see a link to login. If this is disabled DJs and admins will need diff --git a/docs/manual/getting-started/index.md b/docs/manual/getting-started/index.md index 7a80a6a52..dc6871b6c 100644 --- a/docs/manual/getting-started/index.md +++ b/docs/manual/getting-started/index.md @@ -94,3 +94,15 @@ your LibreTime server has made to this Icecast server. If you have only just installed LibreTime, there may not be any media playing out yet. ![](static/Screenshot293-Icecast_status_page.png) + +Reverse Proxy Connections +------------------------- +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 +[Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) +to occur. By default, CORS requests are blocked by your browser and the origins +need to be added to the **Allowed CORS URLs** block in +[**General Settings**](/manual/general/). These origins should include any +domains that will be used externally to connect to your reverse proxy that you +want handled by LibreTime. From f42f331c29cf1106580170c5ac9be66ee7576571 Mon Sep 17 00:00:00 2001 From: Robb Ebright Date: Tue, 7 Jan 2020 08:50:32 -0500 Subject: [PATCH 14/23] added xmlstarlet to centos based vagrant install --- installer/vagrant/centos.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/installer/vagrant/centos.sh b/installer/vagrant/centos.sh index ddfcecebd..fdff0dd5f 100644 --- a/installer/vagrant/centos.sh +++ b/installer/vagrant/centos.sh @@ -86,7 +86,8 @@ yum install -y \ policycoreutils-python \ python-celery \ python2-pika \ - lsof + lsof \ + xmlstarlet # for pip ssl install yum install -y \ From 336e7d82b19165bb76c2e051cc9b2edb5721ed6a Mon Sep 17 00:00:00 2001 From: Robb Ebright Date: Tue, 7 Jan 2020 10:40:30 -0500 Subject: [PATCH 15/23] fixed installer to not change pass during upgrade and add centos support --- install | 21 +++++++++++++-------- installer/vagrant/centos.sh | 2 +- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/install b/install index 897adae30..b64b12621 100755 --- a/install +++ b/install @@ -891,15 +891,20 @@ if [ "$icecast" = "t" ]; then icecast_unit_name="icecast2" if [ "$dist" != "centos" ]; then sed -i 's/ENABLE=false/ENABLE=true/g' /etc/default/icecast2 + icecast_config="/etc/icecast2/icecast.xml" else icecast_unit_name="icecast" + icecast_config="/etc/icecast.xml" + fi + # only update icecast password if + if [ ! -f "/etc/airtime/airtime.conf" ]; then + icecast_pass=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-12};) + echo $icecast_pass > /tmp/icecast_pass + loud "\n New install detected setting icecast password to random value." + xmlstarlet ed --inplace -u /icecast/authentication/source-password -v $icecast_pass $icecast_config + xmlstarlet ed --inplace -u /icecast/authentication/relay-password -v $icecast_pass $icecast_config + xmlstarlet ed --inplace -u /icecast/authentication/admin-password -v $icecast_pass $icecast_config fi - icecast_pass=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-12};) - echo $icecast_pass > /tmp/icecast_pass - xmlstarlet ed --inplace -u /icecast/authentication/source-password -v $icecast_pass /etc/icecast2/icecast.xml - xmlstarlet ed --inplace -u /icecast/authentication/relay-password -v $icecast_pass /etc/icecast2/icecast.xml - xmlstarlet ed --inplace -u /icecast/authentication/admin-password -v $icecast_pass /etc/icecast2/icecast.xml - # restart in case icecast was already started (like is the case on debian) systemInitCommand restart ${icecast_unit_name} verbose "...Done" @@ -1104,10 +1109,10 @@ if [ ! -d "/etc/airtime" ]; then verbose "\n * Creating /etc/airtime/ directory..." mkdir /etc/airtime + # need to copy the icecast_pass from temp to /etc/airtime so installer can read it + cp /tmp/icecast_pass /etc/airtime/icecast_pass fi -# need to copy the icecast_pass from temp to /etc/airtime so installer can read it - cp /tmp/icecast_pass /etc/airtime/icecast_pass chown -R ${web_user}:${web_user} /etc/airtime diff --git a/installer/vagrant/centos.sh b/installer/vagrant/centos.sh index fdff0dd5f..ed3b0bf70 100644 --- a/installer/vagrant/centos.sh +++ b/installer/vagrant/centos.sh @@ -86,7 +86,7 @@ yum install -y \ policycoreutils-python \ python-celery \ python2-pika \ - lsof \ + lsof \ xmlstarlet # for pip ssl install From 8000e1936aa674e01de7bdd6029e80fc2a1833f4 Mon Sep 17 00:00:00 2001 From: Kyle Robbertze Date: Wed, 8 Jan 2020 15:09:04 +0200 Subject: [PATCH 16/23] add default icecast admin username Fixes: #879 --- airtime_mvc/build/sql/defaultdata.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airtime_mvc/build/sql/defaultdata.sql b/airtime_mvc/build/sql/defaultdata.sql index cd976b11f..b05d6a856 100644 --- a/airtime_mvc/build/sql/defaultdata.sql +++ b/airtime_mvc/build/sql/defaultdata.sql @@ -32,7 +32,7 @@ INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s1_host', '1 INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s1_port', '8000', 'integer'); INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s1_user', '', 'string'); INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s1_pass', 'hackme', 'string'); -INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s1_admin_user', '', 'string'); +INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s1_admin_user', 'admin', 'string'); INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s1_admin_pass', '', 'string'); INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s1_mount', 'airtime_128', 'string'); INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s1_url', 'https://libretime.org', 'string'); From 60b08dff4ff30704c613cd202b8c6a44958dcd58 Mon Sep 17 00:00:00 2001 From: Kyle Robbertze Date: Thu, 9 Jan 2020 14:41:12 +0200 Subject: [PATCH 17/23] add default for streams 2-4 --- airtime_mvc/build/sql/defaultdata.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/airtime_mvc/build/sql/defaultdata.sql b/airtime_mvc/build/sql/defaultdata.sql index b05d6a856..82b27c1e0 100644 --- a/airtime_mvc/build/sql/defaultdata.sql +++ b/airtime_mvc/build/sql/defaultdata.sql @@ -47,7 +47,7 @@ INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s2_host', '' INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s2_port', '', 'integer'); INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s2_user', '', 'string'); INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s2_pass', '', 'string'); -INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s2_admin_user', '', 'string'); +INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s2_admin_user', 'admin', 'string'); INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s2_admin_pass', '', 'string'); INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s2_mount', '', 'string'); INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s2_url', '', 'string'); @@ -62,7 +62,7 @@ INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s3_host', '' INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s3_port', '', 'integer'); INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s3_user', '', 'string'); INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s3_pass', '', 'string'); -INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s3_admin_user', '', 'string'); +INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s3_admin_user', 'admin', 'string'); INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s3_admin_pass', '', 'string'); INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s3_mount', '', 'string'); INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s3_url', '', 'string'); @@ -370,7 +370,7 @@ INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s4_host', '' INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s4_port', '', 'integer'); INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s4_user', '', 'string'); INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s4_pass', '', 'string'); -INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s4_admin_user', '', 'string'); +INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s4_admin_user', 'admin', 'string'); INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s4_admin_pass', '', 'string'); INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s4_mount', '', 'string'); INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s4_url', '', 'string'); From 1c89675f6e5a7302a855b16eb901cc5357dcd085 Mon Sep 17 00:00:00 2001 From: Robb Ebright Date: Thu, 9 Jan 2020 07:58:29 -0500 Subject: [PATCH 18/23] made sure icecast_pass was copied to airtime directory upon reprovision --- install | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/install b/install index 216ba0b90..613597a22 100755 --- a/install +++ b/install @@ -1118,11 +1118,12 @@ if [ ! -d "/etc/airtime" ]; then verbose "\n * Creating /etc/airtime/ directory..." mkdir /etc/airtime - # need to copy the icecast_pass from temp to /etc/airtime so installer can read it - cp /tmp/icecast_pass /etc/airtime/icecast_pass fi - +if [ ! -f "/etc/airtime/airtime.conf" ]; then + # need to copy the icecast_pass from temp to /etc/airtime so web-based installer can read it + cp /tmp/icecast_pass /etc/airtime/icecast_pass +fi chown -R ${web_user}:${web_user} /etc/airtime From 4be7699eec2100ab0caf6e6235f7c9306e52b442 Mon Sep 17 00:00:00 2001 From: Robb Ebright Date: Thu, 9 Jan 2020 08:36:52 -0500 Subject: [PATCH 19/23] made sure that icecast pass is not changed upon upgrade or reprovision --- install | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install b/install index 613597a22..bb62d98b1 100755 --- a/install +++ b/install @@ -906,7 +906,7 @@ if [ "$icecast" = "t" ]; then icecast_config="/etc/icecast.xml" fi # only update icecast password if - if [ ! -f "/etc/airtime/airtime.conf" ]; then + if [ ! -f "/etc/airtime/airtime.conf" ] && [ !-f "/etc/airtime/airtime.conf.tmp" ]; then icecast_pass=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-12};) echo $icecast_pass > /tmp/icecast_pass loud "\n New install detected setting icecast password to random value." @@ -1120,7 +1120,7 @@ if [ ! -d "/etc/airtime" ]; then mkdir /etc/airtime fi -if [ ! -f "/etc/airtime/airtime.conf" ]; then +if [ ! -f "/etc/airtime/airtime.conf" ] && [ !-f "/etc/airtime/airtime.conf.tmp" ]; then # need to copy the icecast_pass from temp to /etc/airtime so web-based installer can read it cp /tmp/icecast_pass /etc/airtime/icecast_pass fi From 7c524fb0afbb746ca02b38ecd5ccaeacf2937ea3 Mon Sep 17 00:00:00 2001 From: Codenift Date: Wed, 22 Jan 2020 15:02:10 -0500 Subject: [PATCH 20/23] Quick fix for install Work around until #951 is merged. Helps with the issue many are having by installing a previous package of zipp, 1.0.0 --- install | 1 + 1 file changed, 1 insertion(+) diff --git a/install b/install index 1dcd695b5..b2db21f29 100755 --- a/install +++ b/install @@ -905,6 +905,7 @@ loud "-----------------------------------------------------" verbose "\n * Installing necessary python services..." loudCmd "pip install setuptools --upgrade" +loudCmd "pip install zipp==1.0.0" verbose "...Done" # Ubuntu Trusty and Debian Wheezy needs a workaround for python version SSL downloads From f6f536c749ac8b6ac0c503411142b7ffaafc266b Mon Sep 17 00:00:00 2001 From: Keoni Mahelona Date: Wed, 19 Feb 2020 04:01:11 +0000 Subject: [PATCH 21/23] Set mutagen~=1.43.0 until LibreTime moves to python3 --- python_apps/airtime_analyzer/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python_apps/airtime_analyzer/setup.py b/python_apps/airtime_analyzer/setup.py index e59b34593..eb697d941 100644 --- a/python_apps/airtime_analyzer/setup.py +++ b/python_apps/airtime_analyzer/setup.py @@ -28,7 +28,7 @@ setup(name='airtime_analyzer', packages=['airtime_analyzer'], scripts=['bin/airtime_analyzer'], install_requires=[ - 'mutagen>=1.41.1', # got rid of specific version requirement + 'mutagen~=1.43.0', # got rid of specific version requirement 'pika', 'daemon', 'file-magic', From dc2250c21263cc816f84665a8cb8104c60cc0180 Mon Sep 17 00:00:00 2001 From: Codenift Date: Thu, 27 Feb 2020 18:59:14 -0500 Subject: [PATCH 22/23] live info api fix --- .../application/controllers/ApiController.php | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/airtime_mvc/application/controllers/ApiController.php b/airtime_mvc/application/controllers/ApiController.php index 17da20b97..212cd7c8b 100644 --- a/airtime_mvc/application/controllers/ApiController.php +++ b/airtime_mvc/application/controllers/ApiController.php @@ -297,17 +297,23 @@ class ApiController extends Zend_Controller_Action $stationUrl = Application_Common_HTTPHelper::getStationUrl(); - $previousID = $result["previous"]["metadata"]["id"]; - $get_prev_artwork_url = $stationUrl . 'api/track?id='. $previousID .'&return=artwork'; - $result["previous"]["metadata"]["artwork_url"] = $get_prev_artwork_url; + if ($result["previous"]["type"] != "livestream") { + $previousID = $result["previous"]["metadata"]["id"]; + $get_prev_artwork_url = $stationUrl . 'api/track?id='. $previousID .'&return=artwork'; + $result["previous"]["metadata"]["artwork_url"] = $get_prev_artwork_url; + } - $currID = $result["current"]["metadata"]["id"]; - $get_curr_artwork_url = $stationUrl . 'api/track?id='. $currID .'&return=artwork'; - $result["current"]["metadata"]["artwork_url"] = $get_curr_artwork_url; + if ($result["current"]["type"] != "livestream") { + $currID = $result["current"]["metadata"]["id"]; + $get_curr_artwork_url = $stationUrl . 'api/track?id='. $currID .'&return=artwork'; + $result["current"]["metadata"]["artwork_url"] = $get_curr_artwork_url; + } - $nextID = $result["previous"]["metadata"]["id"]; - $get_next_artwork_url = $stationUrl . 'api/track?id='. $nextID .'&return=artwork'; - $result["previous"]["metadata"]["artwork_url"] = $get_next_artwork_url; + if ($result["next"]["type"] != "livestream") { + $nextID = $result["next"]["metadata"]["id"]; + $get_next_artwork_url = $stationUrl . 'api/track?id='. $nextID .'&return=artwork'; + $result["next"]["metadata"]["artwork_url"] = $get_next_artwork_url; + } // apply user-defined timezone, or default to station Application_Common_DateHelper::convertTimestampsToTimezone( From 4b3bff7c29cd32b78daadde67ff42836eeead97f Mon Sep 17 00:00:00 2001 From: Keoni Mahelona Date: Wed, 11 Mar 2020 22:57:17 +1300 Subject: [PATCH 23/23] Add silan package to Debian Buster install --- installer/lib/requirements-debian-buster.apt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/installer/lib/requirements-debian-buster.apt b/installer/lib/requirements-debian-buster.apt index 5c665ec07..aceab8a25 100644 --- a/installer/lib/requirements-debian-buster.apt +++ b/installer/lib/requirements-debian-buster.apt @@ -59,7 +59,7 @@ libfaad2 php-apcu lame - +silan coreutils liquidsoap