├── .gitignore ├── COPYING ├── README.md ├── VERSION ├── default_tos.txt ├── dependencies.txt ├── docs ├── OBPlayerStyleGuide.md ├── OBPlayerTools.md └── RavennaAoIP.md ├── install.txt ├── obplayer.py ├── obplayer ├── __init__.py ├── alert_counter.py ├── alerts │ ├── __init__.py │ ├── alert.py │ ├── data │ │ ├── attention-signal.ogg │ │ ├── canadian-attention-signal.mp3 │ │ └── testalerts │ │ │ ├── 1example_CAPCP_No_Attachment.xml │ │ │ ├── 2016_03_01T07_54_04_04_00IC0B0D06C_882E_1195_7170_24D9BF99FAD9.xml │ │ │ ├── 2019-01-01T00_04_07_02Iurn3oid32.49.0.1.124.0322195743.2019_001.xml │ │ │ ├── 2example_CAPCP_with_Embedded_Large_Audio_File.xml │ │ │ ├── 4example_CAPCP_with_External_Large_Audio_File.xml │ │ │ └── NWS-IDP-PROD-3296165-2882593.xml │ ├── processor.py │ └── triggers │ │ ├── __init__.py │ │ ├── ledsign.py │ │ ├── rs232.py │ │ └── streamer.py ├── aoipin │ └── __init__.py ├── audiolog │ ├── __init__.py │ ├── audiolog.py │ └── uploader.py ├── data.py ├── fallback │ ├── __init__.py │ └── fallback_player.py ├── gui.py ├── httpadmin │ ├── __init__.py │ ├── http │ │ ├── alertdetails.html │ │ ├── common.css │ │ ├── common.js │ │ ├── extras │ │ │ ├── images │ │ │ │ ├── layers-2x.png │ │ │ │ ├── layers.png │ │ │ │ ├── marker-icon-2x.png │ │ │ │ ├── marker-icon.png │ │ │ │ └── marker-shadow.png │ │ │ ├── jquery.min.js │ │ │ ├── leaflet.css │ │ │ ├── leaflet.draw.js │ │ │ ├── leaflet.js │ │ │ ├── select2.min.css │ │ │ └── select2.min.js │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── logs.html │ │ ├── map.js │ │ └── oblogo.svg │ ├── httpadmin.py │ ├── httpserver.py │ ├── pyhtml.py │ └── strings │ │ └── default │ │ ├── admin_tab.txt │ │ ├── alert_details.txt │ │ ├── alerts_tab.txt │ │ ├── common.txt │ │ ├── http_tab.txt │ │ ├── liveassist_tab.txt │ │ ├── location_tab.txt │ │ ├── misc.txt │ │ ├── outputs_tab.txt │ │ ├── responses.txt │ │ ├── sources_tab.txt │ │ ├── status_tab.txt │ │ ├── streamer_tab.txt │ │ ├── summary_tab.txt │ │ └── sync_tab.txt ├── linein │ └── __init__.py ├── liveassist │ ├── __init__.py │ ├── http │ │ ├── adapter.js │ │ ├── common.js │ │ ├── index.html │ │ ├── jquery-ui-1.10.4.custom.min.js │ │ ├── jquery-ui-css │ │ │ └── smoothness │ │ │ │ ├── images │ │ │ │ ├── animated-overlay.gif │ │ │ │ ├── ui-bg_flat_0_aaaaaa_40x100.png │ │ │ │ ├── ui-bg_flat_75_ffffff_40x100.png │ │ │ │ ├── ui-bg_glass_55_fbf9ee_1x400.png │ │ │ │ ├── ui-bg_glass_65_ffffff_1x400.png │ │ │ │ ├── ui-bg_glass_75_dadada_1x400.png │ │ │ │ ├── ui-bg_glass_75_e6e6e6_1x400.png │ │ │ │ ├── ui-bg_glass_95_fef1ec_1x400.png │ │ │ │ ├── ui-bg_highlight-soft_75_cccccc_1x100.png │ │ │ │ ├── ui-icons_222222_256x240.png │ │ │ │ ├── ui-icons_2e83ff_256x240.png │ │ │ │ ├── ui-icons_454545_256x240.png │ │ │ │ ├── ui-icons_888888_256x240.png │ │ │ │ └── ui-icons_cd0a0a_256x240.png │ │ │ │ ├── jquery-ui-1.10.4.custom.css │ │ │ │ └── jquery-ui-1.10.4.custom.min.css │ │ ├── jquery.min.js │ │ ├── oldwebrtc.js │ │ ├── stream.html │ │ ├── stream.js │ │ ├── style.css │ │ └── svg │ │ │ ├── next.svg │ │ │ ├── pause.svg │ │ │ ├── play.svg │ │ │ └── prev.svg │ ├── liveassist.py │ └── microphone.py ├── log.py ├── main.py ├── newsfeed_override │ ├── __init__.py │ └── override.py ├── offair_audiolog │ ├── __init__.py │ ├── audiolog.py │ └── recorder.py ├── override_streamer │ └── __init__.py ├── password_system.py ├── player │ ├── __init__.py │ ├── control.py │ ├── outputs.py │ ├── overlay.py │ ├── pipes │ │ ├── __init__.py │ │ ├── base.py │ │ ├── breakbin.py │ │ ├── decodebin.py │ │ ├── image.py │ │ ├── linein.py │ │ ├── remote_audio.py │ │ ├── rtp.py │ │ ├── rtsp.py │ │ ├── rtspa.py │ │ ├── sdp.py │ │ └── testsignal.py │ └── playlog.py ├── pulse │ ├── __init__.py │ └── views │ │ ├── sinks.pyhtml │ │ └── sources.pyhtml ├── rtpin │ └── __init__.py ├── scheduler │ ├── __init__.py │ ├── data.py │ ├── priority.py │ ├── scheduler.py │ └── sync.py ├── streamer │ ├── __init__.py │ ├── avahi.py │ ├── avahi_publish.py │ ├── base.py │ ├── icecast.py │ ├── linein.py │ ├── metadata_updater.py │ ├── rtp.py │ ├── rtsp.py │ └── youtube.py ├── task.py ├── testsignal │ └── __init__.py ├── ui.glade └── xrandr │ └── __init__.py ├── obplayer_check ├── obplayer_loop ├── tools ├── audiolog_concat_example1.sh ├── audiolog_concat_example2.sh ├── audiolog_upload ├── live_monitor ├── livewire-example.sdp ├── local_streamer ├── local_streamer.orig ├── local_streamer.sdp ├── obplayer_monitor ├── obremote_monitor_arecord └── priority_stream └── updater /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | .*.swo 3 | *~ 4 | *.pyc 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenBroadcaster Multimedia Playout 2 | 3 | ## Overview 4 | 5 | OBPlayer is a stable and secure UNIX-based media streaming playout application that can operate as a standalone player or controlled over a network by a managing OBServer. It can be installed remotely at a transmitter site, in the studio or as multiple virtual headless processes. OBPlayer is built with rules based intelligence to continue broadcasting no matter what happens. It functions by continually syncing with OBServer, looking for updated schedules, media, and priority broadcasts. If there is a blank spot in the schedule, it falls back to a Default Playlist. If that fails, it goes into Fallback Media Mode. If that fails, it plays from the analog input bypass. Finally, it will play a test signal as a last resort. OBPlayer will always play valid CAP (Common Alerting Protocol) Alerts at the highest priority. 6 | 7 | OBPlayer can be run in a variety of configurations: 8 | 9 | + Headless OBPlayer (CLI Process) 10 | + LIVE Assist with Mobile HTML5 Touch Screen interface 11 | + GTK desktop application for a Digital Display and output to CATV 12 | + Standalone Emergency Alerting CAP Player supporting audio, image and video 13 | + Support For IPAWS CAP Profile Version 1.0 via [Alert-Hub](https://www.alert-hub.org/) 14 | 15 | ## Installation 16 | 17 | To install OBPlayer, you should have a basic understanding of the Linux shell terminal. Once installed, every aspect of your station is managed via OBPlayer's web interface. 18 | 19 | Follow our [Install instructions](https://github.com/openbroadcaster/obplayer/blob/main/install.txt) and Post Installation [Troubleshooting guide](https://support.openbroadcaster.com/troubleshooting#post-installation-obplayer-troubleshooting) for instructions on how to setup on your own hardware. 20 | 21 | ## Support 22 | 23 | If you need help with OpenBroadcaster, the first place you should visit is our [Support](https://support.openbroadcaster.com/) page, which features solutions to a number of commonly encountered issues and questions. 24 | 25 | ## FAQ 26 | 27 | Users have sent us [Frequently Asked Questions](https://openbroadcaster.com/knowledge/frequently-asked-questions) and asked common technical questions. 28 | 29 | ## Documentation 30 | 31 | Documentation and user guides can be found in the project's [Support](https://support.openbroadcaster.com) website: 32 | 33 | ## Change Log 34 | 35 | Visit our [Change Log](https://openbroadcaster.com/resource/change-log) and project history. 36 | 37 | ## API Access 38 | 39 | All of the functionality and metadata in OBServer, (Media, Playlists,Scheduling) is available through our [Documented API](https://docs.openbroadcaster.com/) 40 | 41 | ## New feature requests 42 | 43 | You can visit our GitHub [Issues](https://github.com/openbroadcaster/obplayer/issues) pages to submit a new feature request or comment on existing projects. 44 | 45 | ## Bug and error reports 46 | 47 | We rely exclusively on our GitHub [Issues](https://github.com/openbroadcaster/obplayer/issues) to diagnose, track and update these reports. First, check to make sure the issue you're experiencing isn't already reported. If it is, you can subscribe to the existing ticket for updates on the issue's progress. If your issue or request isn't already reported, click the "New Issue" button to create it. Make sure to follow the template provided, as it asks important details that are very important to our team. 48 | 49 | ## Contributing 50 | 51 | Contributions are more than welcome! You can help us improve OpenBroadcaster by either contributing to the core OBServer or OBPlayer, creating a new module or extending language translations and self help guides. 52 | 53 | Any help is welcome, big or small. We are all learning together. 54 | 55 | See our [Contributing Guide](https://github.com/openbroadcaster/.github/blob/main/docs/CONTRIBUTING.md) to learn how to get involved. 56 | 57 | Please check out the various GIT projects and components on [OpenBroadcaster](https://github.com/openbroadcaster) 58 | 59 | ## Releases and Versioning 60 | 61 | See our [Releases Guide](https://github.com/openbroadcaster/.github/blob/main/docs/RELEASES.md) 62 | 63 | ## Code of Conduct 64 | 65 | We abide by our [Code of Conduct](https://github.com/openbroadcaster/.github/blob/main/docs/CODE_OF_CONDUCT.md) and feel strongly about open, appreciative, and empathetic people joining us. 66 | 67 | ## Support OpenBroadcaster 68 | 69 | OpenBroadcaster will always be available free of charge. OpenBroadcaster is built and maintained by passionate volunteers and broadcasters, so there may be some delays in getting back to you. We make the best effort possible to resolve issues and answer questions. If you find the software support the project. Your support is greatly appreciated. 70 | 71 | Copyright 2012-2024 OpenBroadcaster Inc. 72 | 73 | Licensed under GNU AGPLv3. See COPYING. 74 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 5.1.1-20230131 2 | -------------------------------------------------------------------------------- /dependencies.txt: -------------------------------------------------------------------------------- 1 | Dependencies for OpenBroadcaster OBPlayer. This list may be incomplete. 2 | 3 | Dependencies are Debian/Ubuntu package names. 4 | 5 | - ntp 6 | - python3 7 | - python3-pycurl 8 | - python3-openssl 9 | - python3-apsw 10 | - python3-magic 11 | 12 | - python3-dateutil 13 | - python3-requests 14 | 15 | - python3-gi 16 | - python3-gi-cairo 17 | - gir1.2-gtk-3.0 18 | - gir1.2-gdkpixbuf-2.0 19 | - gir1.2-pango-1.0 20 | 21 | - python3-gst-1.0 22 | - gir1.2-gstreamer-1.0 23 | - gir1.2-gst-plugins-base-1.0 24 | - gir1.2-gst-rtsp-server-1.0 25 | 26 | - gstreamer1.0-tools 27 | - gstreamer1.0-libav 28 | - gstreamer1.0-alsa 29 | - gstreamer1.0-pulseaudio 30 | - gstreamer1.0-plugins-base 31 | - gstreamer1.0-plugins-good 32 | - gstreamer1.0-plugins-bad 33 | - gstreamer1.0-plugins-ugly 34 | 35 | - ffmpeg 36 | 37 | Ubuntu 38 | 39 | - ubuntu-restricted-addons 40 | - ubuntu-restricted-extras 41 | 42 | - pip3 install passlib[bcrypt] 43 | 44 | Recommended for CATV Video Playout: 45 | 46 | - gstreamer1.0-vaapi 47 | - mesa-vdpau-drivers 48 | 49 | Include if using alerts module: 50 | 51 | - espeak 52 | - mbrola 53 | - mbrola-en1 54 | - mbrola-us1 55 | - mbrola-us2 56 | - mbrola-us3 57 | - mbrola-fr1 58 | - mbrola-fr4 59 | 60 | Include if using POLLY AWS Voices in the alerts module: 61 | 62 | - pip3 install boto3 63 | 64 | Include if using RS-232 trigger option in the alerts module: 65 | 66 | - python3-serial 67 | 68 | Include if sharing multiple OpenBroadcaster players with a local media library: 69 | 70 | - cifs-utils 71 | OR 72 | - autofs 73 | 74 | Command Line tool for PulseAudio: 75 | 76 | - pip3 install pulsectl 77 | 78 | Off-air audio log and SDR FM Receiver USB Dongle: 79 | 80 | - pip3 install pyrtlsdr 81 | 82 | Include if using the news feed override: 83 | 84 | - pip3 install inotify 85 | 86 | Note: pip3 pkg not included in apt-get; must be installed using pip3 87 | -------------------------------------------------------------------------------- /docs/OBPlayerStyleGuide.md: -------------------------------------------------------------------------------- 1 | # OBPlayer Code Style Guide 2 | 3 | OBPlayer rewrite supports Python 3 4 | 5 | OBPlayer was originally written in Python (v2) and uses the style guide described at http://www.python.org/ 6 | 7 | Exceptions 8 | 9 | 2-space tabs are used rather than 4-space tabs. 10 | 11 | In case of other consistent exceptions to the Python style guide, follow the style used in the Remote code. In case of other inconsistent exceptions to the Python style guide, follow the Python style guide. This section should be updated with other consistent exceptions. Important Style Elements 12 | 13 | The following are important style elements consistent with the Python style guide. 14 | 15 | Spaces are used rather than tabs. 16 | 17 | Naming conventions should follow the Python style guide strictly: 18 | 19 | - ClassNamesUseCapsWords 20 | 21 | - function_names, method_names, variable_names use lowercase_with_underscores. 22 | 23 | - CONSTANTS use UPPERCASE_WITH_UNDERSCORES. 24 | -------------------------------------------------------------------------------- /docs/OBPlayerTools.md: -------------------------------------------------------------------------------- 1 | # OBPlayer Tools 2 | 3 | ## CLI Options 4 | 5 | Bash script "obplayer_check" starts the player and checks for existing instances and not start if already running 6 | 7 | Bash script "obplayer_loop" runs in an infinite loop to restart in case it unexpectedly terminates (crashes) or is shutdown via the web dashboard 8 | 9 | obplayer -d prints log messages to console 10 | 11 | obplayer -f or obplayer--fullscreen on startup. (obplayer_loop -f also works) 12 | 13 | obplayer -h help (displays command-line options) 14 | 15 | obplayer -H starts headless, no desktop GUI 16 | 17 | obplayer -m starts screen minimized 18 | 19 | obplayer -r restarts fresh, clearing out Playlist\Schedule\Media cache 20 | 21 | obplayer -c CONFIGDIR, –configdir Specifies an alternate data directory (default: [’~/.openbroadcaster’]) 22 | 23 | obplayer – disable-updater Disables the OS updater 24 | 25 | obplayer – disable-http Disables the http admin dashboard 26 | 27 | ## Trigger FallBack Media 28 | 29 | To get fallback media to play by causing simulated network problems, remove the default playlist, disable Scheduler. 30 | 31 | 1. start obplayer application with "./obplayer -r" 32 | 33 | 2. immediately quit before it has a chance to sync 34 | 35 | 3. start obplayer again but this time with just "./obplayer" 36 | 37 | ## Test video playback via gstreamer 38 | 39 | ~~~~ 40 | gst-launch-1.0 playbin2 uri="file:///absolute/path/to/file" 41 | ~~~~ 42 | -------------------------------------------------------------------------------- /install.txt: -------------------------------------------------------------------------------- 1 | OBPlayer Installation Instructions 2 | 3 | 1. Check dependencies.txt for player dependencies on Debian 10/Ubuntu 20.04 & above. 4 | 5 | 2. Run "bash obplayer_check" or "bash obplayer_loop" or "bash obplayer_check -h" for assistance. 6 | 7 | 3. The settings.db configuration file is generated on the first run in ~/.openbroadcaster. 8 | 9 | 4. Access the admin panel at http://:23233 with default credentials (user=admin, password=admin). 10 | 11 | 5. Configure tabbed menus as needed. 12 | 13 | 6. Restart the player to apply the changes. 14 | 15 | Note: Users must update their login passwords on their first login using the default credentials. 16 | 17 | For more detailed instructions, please refer to https://support.openbroadcaster.com/install 18 | -------------------------------------------------------------------------------- /obplayer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright 2012-2015 OpenBroadcaster, Inc. 6 | 7 | This file is part of OpenBroadcaster Player. 8 | 9 | OpenBroadcaster Player is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Affero General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | OpenBroadcaster Player is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Affero General Public License for more details. 18 | 19 | You should have received a copy of the GNU Affero General Public License 20 | along with OpenBroadcaster Player. If not, see . 21 | """ 22 | 23 | import obplayer 24 | 25 | obplayer.main() 26 | 27 | -------------------------------------------------------------------------------- /obplayer/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright 2012-2015 OpenBroadcaster, Inc. 6 | 7 | This file is part of OpenBroadcaster Player. 8 | 9 | OpenBroadcaster Player is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Affero General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | OpenBroadcaster Player is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Affero General Public License for more details. 18 | 19 | You should have received a copy of the GNU Affero General Public License 20 | along with OpenBroadcaster Player. If not, see . 21 | """ 22 | 23 | from __future__ import absolute_import 24 | 25 | from obplayer.task import ObThread 26 | from obplayer.log import ObLog 27 | from obplayer.data import ObData, ObConfigData 28 | from obplayer.main import ObMainApp 29 | from obplayer.gui import ObGui 30 | import obplayer.password_system as password_system 31 | from obplayer.alert_counter import Alert_Counter 32 | 33 | Log = None 34 | Config = None 35 | 36 | Gui = None 37 | Main = None 38 | 39 | def main(): 40 | ObMainApp().start() 41 | 42 | -------------------------------------------------------------------------------- /obplayer/alert_counter.py: -------------------------------------------------------------------------------- 1 | import obplayer 2 | import pickle 3 | import os 4 | 5 | class Alert_Counter(object): 6 | def __init__(self): 7 | self.datadir = obplayer.ObData.get_datadir() 8 | self.data_file = self.datadir + '/.alerts' 9 | self.alerts = {'local_tests': [], 'advisorys': [], 'broadcast_intrusive': []} 10 | 11 | #load data from last use. 12 | if os.access(self.data_file, os.F_OK): 13 | with open(self.data_file, 'rb') as file: 14 | self.alerts = pickle.load(file) 15 | 16 | 17 | def add_alert(self, alert_id, alert_type): 18 | if self.is_already_logged(alert_id): 19 | obplayer.Log.log("alert already logged: {0}.".format(alert_id), 'alert counter') 20 | else: 21 | if alert_type == 'Local Test Alert': 22 | self.alerts['local_tests'].append(alert_id) 23 | elif alert_type == 'Advisory Alert': 24 | self.alerts['advisorys'].append(alert_id) 25 | elif alert_type == 'Broadcast Intrusive Alert': 26 | self.alerts['broadcast_intrusive'].append(alert_id) 27 | else: 28 | obplayer.Log.log("couldn't log alert: {0} with type {1}!".format(alert_id, alert_type), 'alerts') 29 | self.save_data() 30 | 31 | def is_already_logged(self, alert_id): 32 | alert_types = ['local_tests', 'advisorys', 'broadcast_intrusive'] 33 | for alert_type in alert_types: 34 | for alert in self.alerts[alert_type]: 35 | if alert_id == alert: 36 | return True 37 | return False 38 | 39 | def get_number_of_alerts(self, alert_type): 40 | if alert_type == 'local_test': 41 | return len(self.alerts['local_tests']) 42 | elif alert_type == 'advisory': 43 | return len(self.alerts['advisorys']) 44 | elif alert_type == 'broadcast_intrusive': 45 | return len(self.alerts['broadcast_intrusive']) 46 | else: 47 | return None 48 | 49 | def save_data(self): 50 | data = pickle.dumps(self.alerts) 51 | #print(data) 52 | with open(self.data_file, 'wb') as file: 53 | file.write(data) 54 | -------------------------------------------------------------------------------- /obplayer/alerts/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright 2012-2015 OpenBroadcaster, Inc. 6 | 7 | This file is part of OpenBroadcaster Player. 8 | 9 | OpenBroadcaster Player is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Affero General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | OpenBroadcaster Player is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Affero General Public License for more details. 18 | 19 | You should have received a copy of the GNU Affero General Public License 20 | along with OpenBroadcaster Player. If not, see . 21 | """ 22 | 23 | from __future__ import absolute_import 24 | 25 | from obplayer.alerts.alert import ObAlert, parse_alert_file 26 | from obplayer.alerts.processor import ObAlertProcessor 27 | 28 | Processor = None 29 | 30 | def init(): 31 | global Processor 32 | Processor = ObAlertProcessor() 33 | 34 | def quit(): 35 | pass 36 | 37 | -------------------------------------------------------------------------------- /obplayer/alerts/data/attention-signal.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openbroadcaster/obplayer/5f18f0df5fe31540014b7175c225b415c8018a50/obplayer/alerts/data/attention-signal.ogg -------------------------------------------------------------------------------- /obplayer/alerts/data/canadian-attention-signal.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openbroadcaster/obplayer/5f18f0df5fe31540014b7175c225b415c8018a50/obplayer/alerts/data/canadian-attention-signal.mp3 -------------------------------------------------------------------------------- /obplayer/alerts/data/testalerts/1example_CAPCP_No_Attachment.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 739C-244C-43D4-C936-BCA1-57B6 4 | obplayer@localhost 5 | 2021-02-18T17:13:00-05:00 6 | Actual 7 | Alert 8 | Public 9 | profile:CAP-CP:0.4 10 | layer:SOREM:1.0 11 | 12 | en-CA 13 | Met 14 | testMessage 15 | Immediate 16 | Extreme 17 | Observed 18 | 19 | profile:CAP-CP:Event:0.4 20 | testMessage 21 | 22 | OB self test 23 | Sample Alert Message No Attachment 24 | This is only a test. This example includes an Alert Message with NAAD System Signature without an attachment. 25 | 26 | layer:SOREM:1.0:Broadcast_Immediately 27 | Yes 28 | 29 | 30 | Tsiigehtchic, Northwest Territories 31 | 51.502849,-99.263657 51.498802,-99.26395 51.497321,-99.266635 51.49194,-99.264386 51.490075,-99.266247 51.479374,-99.262484 51.473999,-99.265706 51.474256,-99.204523 51.503636,-99.204828 51.503875,-99.193096 51.532352,-99.192697 51.535124,-99.19768 51.537876,-99.19726 51.553234,-99.202605 51.549832,-99.209311 51.548486,-99.207949 51.543869,-99.209961 51.545245,-99.215015 51.551858,-99.217815 51.551656,-99.215103 51.55781,-99.218074 51.576399,-99.220862 51.571719,-99.223234 51.559342,-99.225395 51.552488,-99.236193 51.538291,-99.244281 51.529917,-99.245778 51.528724,-99.256742 51.523192,-99.261972 51.516722,-99.263953 51.510717,-99.263414 51.507501,-99.265974 51.504579,-99.265929 51.502849,-99.263657 32 | 33 | profile:CAP-CP:Location:0.3 34 | 4619068 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | JjHGyYcyeFsdXlERVC+BBArXS4Y= 48 | 49 | 50 | DoxlVdpXe/NdFF22Lge68kEPRyfESz7YqBootog4AgWu7kIyzM6BVjoIk0XF+KL14LqTgZAR22ZtjJPR+gjn1PCoH8bsdPRhpgBrFoMBfj9A5dTCsitksox+3RstcAbdigcUcrmHZNgt3XV06oWX2eA8vcf3l8+vo/lf1JQBdZPvmw+YkukTAoQWHGPs0rBweSYa6lF7fdJAfkjNWRYsJ+tO6JnG+zwg+ANwAb7r9fxfvaYQSG0mYr7wImsm9H+3dkGLQkQjVpi+mdQWePNNHPCjr7nxCCf1JcqzxVoFq8mGsQEbHHc0TQLDEr+159mRj9EHag0lDUBEfv/Jd5++Dg== 51 | 52 | 53 | MIIF0jCCBLqgAwIBAgIQQ4q3qW4WAZEmJ2Y55X+ovjANBgkqhkiG9w0BAQUFADCBtTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTswOQYDVQQLEzJUZXJtcyBvZiB1c2UgYXQgaHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYSAoYykxMDEvMC0GA1UEAxMmVmVyaVNpZ24gQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzMwHhcNMTIwNTI5MDAwMDAwWhcNMTgwNTMwMjM1OTU5WjCBvjELMAkGA1UEBhMCQ0ExEDAOBgNVBAgTB09udGFyaW8xETAPBgNVBAcUCE9ha3ZpbGxlMRwwGgYDVQQKFBNQZWxtb3JleCBNZWRpYSBJbmMuMRswGQYDVQQLFBJOZXR3b3JrIE9wZXJhdGlvbnMxMzAxBgNVBAsUKlRlcm1zIG9mIHVzZSBhdCB3d3cudmVyaXNpZ24uY29tL3JwYSAoYykwNTEaMBgGA1UEAxQRZHNzMS5wZWxtb3JleC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDhBfGX2hawqJR+ZJqLOgHYJw+/dy3VBeAEkGMvC8Gk0sOsPLzc+4mPLSpy9ufOM7EHrZAGh10ZQPWQTUhC6hzJgEhEA5SXTGVM4cDJjyqtKZcPcvFr0rA9Meq1nX7yfquBF0jcjWISa1H5w48MeTjppcpEYYrLXXKrP76+kuStFy9KyvTXiDAYW+zSLKqSWp83diM3BlDYUUeB9VZPBRqgmZbmudJmAfMhN676/uNbEDcLOmTpp68nh68U6HkjFUJD1Mm8ko3pmcpBOBuxsaTbYWJX7cTG6MUU/y45zS6NJ+7+zTv+crojy3mMeQswKdtKqNxgjWMC2asEpSJLbTARAgMBAAGjggHRMIIBzTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIFoDBFBgNVHR8EPjA8MDqgOKA2hjRodHRwOi8vU1ZSU2VjdXJlLUczLWNybC52ZXJpc2lnbi5jb20vU1ZSU2VjdXJlRzMuY3JsMEQGA1UdIAQ9MDswOQYLYIZIAYb4RQEHFwMwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYTAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHwYDVR0jBBgwFoAUDURcFlNEwYJ+HSCrJfQBY9i+eaUwdgYIKwYBBQUHAQEEajBoMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC52ZXJpc2lnbi5jb20wQAYIKwYBBQUHMAKGNGh0dHA6Ly9TVlJTZWN1cmUtRzMtYWlhLnZlcmlzaWduLmNvbS9TVlJTZWN1cmVHMy5jZXIwbgYIKwYBBQUHAQwEYjBgoV6gXDBaMFgwVhYJaW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUS2u5KJYGDLvQUjibKaxLB4shBRgwJhYkaHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nbzEuZ2lmMA0GCSqGSIb3DQEBBQUAA4IBAQA1+UEbk18WrNlP635nBmjjpZOrgwVRwOFUd/29bm9UkCmwS6R2N5OBflKLFSY6wCy8LBg5wZWAJbI/mQ8LQyzDUe+oIpLcMnuoji05NKgSkmEvJ01NTQKki/hlueUN6FTciiVCkMXJ8XdStH9RflKBqZX9IEZzE/b16Cp3MgnxjdFyW1o3l41T0+FucdLE/zvj5CZ6siefw813nTtMGzMu2/jEx6vUJDGBvx2m5Af4CA5924Mfh2xsCLYulSKaHkNV8P+gKdV0+zjrajosbOSDiVWY34Qmvj24JEKLETZFI+AVOSWN559PKKEvT7SirDdFqP0OaD1HV05bes65M+LJ 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /obplayer/alerts/data/testalerts/NWS-IDP-PROD-3296165-2882593.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | NWS-IDP-PROD-3296165-2882593 4 | obplayer@localhost 5 | 2021-02-18T17:13:00-05:00 6 | Actual 7 | Alert 8 | Public 9 | IPAWSv1.0 10 | 11 | 12 | 13 | Met 14 | Small Craft Advisory 15 | Avoid 16 | Expected 17 | Severe 18 | Likely 19 | 20 | SAME 21 | NWS 22 | 23 | 24 | NationalWeatherService 25 | SCY 26 | 27 | 2019-01-02T15:44:00-05:00 28 | 2019-01-02T21:00:00-05:00 29 | NWS Cleveland OH 30 | Small Craft Advisory issued January 2 at 3:44PM EST expiring January 3 at 4:00PM EST by NWS Cleveland OH 31 | The National Weather Service in Cleveland has issued a Small 32 | Craft Advisory, which is in effect from 9 PM this evening to 4 PM 33 | EST Thursday. 34 | This is only a test. 35 | * WINDS...becoming southwest to west 15 to 20 knots with gusts up 36 | to 30 knots. 37 | This is only a test. 38 | * WAVES...building to 3 to 6 feet. 39 | 40 | 41 | This is only a test. 42 | A Small Craft Advisory is issued when waves of 4 feet or more are 43 | expected or wind speeds reach 21 to 33 knots which may produce 44 | hazardous conditions to small craft. Inexperienced mariners... 45 | especially those operating smaller vessels...should stay in port 46 | during these conditions. 47 | 48 | 49 | http://www.weather.gov 50 | 51 | NWSheadline 52 | SMALL CRAFT ADVISORY IN EFFECT FROM 9 PM THIS EVENING TO 4 PM EST THURSDAY 53 | 54 | 55 | VTEC 56 | /O.NEW.KCLE.SC.Y.0002.190103T0200Z-190103T2100Z/ 57 | 58 | 59 | PIL 60 | CLEMWWCLE 61 | 62 | 63 | BLOCKCHANNEL 64 | CMAS 65 | 66 | 67 | BLOCKCHANNEL 68 | EAS 69 | 70 | 71 | BLOCKCHANNEL 72 | NWEM 73 | 74 | 75 | eventEndingTime 76 | 2019-01-03T16:00:00-05:00 77 | 78 | 79 | Avon Point to Willowick OH; Vermilion to Avon Point OH; Willowick to Geneva-on-the Lake OH 80 | 81 | UGC 82 | LEZ146 83 | 84 | 85 | UGC 86 | LEZ145 87 | 88 | 89 | UGC 90 | LEZ147 91 | 92 | 93 | SAME 94 | 096146 95 | 96 | 97 | SAME 98 | 096145 99 | 100 | 101 | SAME 102 | 096147 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /obplayer/alerts/triggers/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright 2012-2015 OpenBroadcaster, Inc. 6 | 7 | This file is part of OpenBroadcaster Player. 8 | 9 | OpenBroadcaster Player is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Affero General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | OpenBroadcaster Player is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Affero General Public License for more details. 18 | 19 | You should have received a copy of the GNU Affero General Public License 20 | along with OpenBroadcaster Player. If not, see . 21 | """ 22 | 23 | from __future__ import absolute_import 24 | 25 | -------------------------------------------------------------------------------- /obplayer/alerts/triggers/rs232.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright 2012-2015 OpenBroadcaster, Inc. 6 | 7 | This file is part of OpenBroadcaster Player. 8 | 9 | OpenBroadcaster Player is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Affero General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | OpenBroadcaster Player is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Affero General Public License for more details. 18 | 19 | You should have received a copy of the GNU Affero General Public License 20 | along with OpenBroadcaster Player. If not, see . 21 | """ 22 | 23 | import obplayer 24 | 25 | import serial 26 | import traceback 27 | 28 | 29 | class SerialTrigger (object): 30 | def __init__(self): 31 | self.trigger_serial = obplayer.Config.setting('alerts_trigger_serial') 32 | self.trigger_serial_file = obplayer.Config.setting('alerts_trigger_serial_file') 33 | self.trigger_serial_fd = None 34 | self.initialize() 35 | 36 | def initialize(self): 37 | try: 38 | obplayer.Log.log("initializing serial trigger on port " + self.trigger_serial_file, 'alerts') 39 | 40 | #serial_fd = serial.Serial(self.trigger_serial_file, baudrate=9600) 41 | #serial_fd.setDTR(False) 42 | #serial_fd.close() 43 | except: 44 | obplayer.Log.log("failed to initalize serial trigger", 'alerts') 45 | obplayer.Log.log(traceback.format_exc(), 'error') 46 | 47 | def alert_cycle_init(self): 48 | pass 49 | 50 | def alert_cycle_each(self, alert, alert_media, processor): 51 | pass 52 | 53 | def alert_cycle_start(self): 54 | try: 55 | obplayer.Log.log("asserted DTR on serial port " + self.trigger_serial_file, 'alerts') 56 | if self.trigger_serial_fd: 57 | self.trigger_serial_fd.close() 58 | self.trigger_serial_fd = serial.Serial(self.trigger_serial_file, baudrate=9600) 59 | self.trigger_serial_fd.setDTR(True) 60 | except: 61 | obplayer.Log.log("failed to assert DTR on serial port " + self.trigger_serial_file, 'alerts') 62 | obplayer.Log.log(traceback.format_exc(), 'error') 63 | 64 | def alert_cycle_stop(self): 65 | try: 66 | obplayer.Log.log("resetting DTR on serial port " + self.trigger_serial_file, 'alerts') 67 | if self.trigger_serial_fd: 68 | self.trigger_serial_fd.setDTR(False) 69 | self.trigger_serial_fd.close() 70 | self.trigger_serial_fd = None 71 | except: 72 | obplayer.Log.log("failed to assert DTR on serial port " + self.trigger_serial_file, 'alerts') 73 | obplayer.Log.log(traceback.format_exc(), 'error') 74 | 75 | -------------------------------------------------------------------------------- /obplayer/alerts/triggers/streamer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright 2012-2015 OpenBroadcaster, Inc. 6 | 7 | This file is part of OpenBroadcaster Player. 8 | 9 | OpenBroadcaster Player is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Affero General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | OpenBroadcaster Player is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Affero General Public License for more details. 18 | 19 | You should have received a copy of the GNU Affero General Public License 20 | along with OpenBroadcaster Player. If not, see . 21 | """ 22 | 23 | import obplayer 24 | 25 | 26 | class StreamerTrigger (object): 27 | def __init__(self): 28 | pass 29 | 30 | def alert_cycle_init(self): 31 | pass 32 | 33 | def alert_cycle_each(self, alert, alert_media, processor): 34 | pass 35 | 36 | def alert_cycle_start(self): 37 | if hasattr(obplayer, 'Streamer_stream_1'): 38 | obplayer.Log.log("starting icecast streamer for alert cycle", 'alerts') 39 | obplayer.Streamer_stream_1.start() 40 | 41 | def alert_cycle_stop(self): 42 | if hasattr(obplayer, 'Streamer_stream_1'): 43 | obplayer.Log.log("stopping icecast streamer after alert cycle", 'alerts') 44 | obplayer.Streamer_stream_1.stop() 45 | 46 | 47 | -------------------------------------------------------------------------------- /obplayer/aoipin/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright 2012-2015 OpenBroadcaster, Inc. 6 | 7 | This file is part of OpenBroadcaster Player. 8 | 9 | OpenBroadcaster Player is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Affero General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | OpenBroadcaster Player is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Affero General Public License for more details. 18 | 19 | You should have received a copy of the GNU Affero General Public License 20 | along with OpenBroadcaster Player. If not, see . 21 | """ 22 | 23 | from __future__ import absolute_import 24 | 25 | import obplayer 26 | 27 | def init(): 28 | def aoip_in_request(self, present_time, media_class): 29 | uri = obplayer.Config.setting('aoip_in_uri') 30 | if uri.startswith('rtsp:'): 31 | self.add_request(media_type='rtsp', uri=uri, duration=31536000) # duration = 1 year (ie. indefinitely) 32 | elif uri.startswith('rtspa:'): 33 | self.add_request(media_type='rtspa', uri=uri.replace('rtspa', 'rtsp'), duration=31536000) # duration = 1 year (ie. indefinitely) 34 | elif uri.startswith('sdp:///'): 35 | self.add_request(media_type='sdp', uri=uri[6:], duration=31536000) # duration = 1 year (ie. indefinitely) 36 | else: 37 | obplayer.Log.log("invalid aoip uri: " + uri, 'error') 38 | 39 | ctrl = obplayer.Player.create_controller('aoipin', priority=20, allow_requeue=False) 40 | ctrl.set_request_callback(aoip_in_request) 41 | 42 | def quit(): 43 | pass 44 | 45 | -------------------------------------------------------------------------------- /obplayer/audiolog/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright 2012-2015 OpenBroadcaster, Inc. 6 | 7 | This file is part of OpenBroadcaster Player. 8 | 9 | OpenBroadcaster Player is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Affero General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | OpenBroadcaster Player is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Affero General Public License for more details. 18 | 19 | You should have received a copy of the GNU Affero General Public License 20 | along with OpenBroadcaster Player. If not, see . 21 | """ 22 | 23 | from __future__ import absolute_import 24 | 25 | import obplayer 26 | 27 | from .audiolog import ObAudioLog 28 | from .uploader import LogUploader 29 | 30 | def init(): 31 | obplayer.AudioLog = ObAudioLog() 32 | if obplayer.Config.setting('audiolog_enable_upload'): 33 | obplayer.LogUploader = LogUploader() 34 | obplayer.LogUploader.start() 35 | 36 | def quit(): 37 | # stop the audio logger. 38 | if hasattr(obplayer, 'AudioLog'): 39 | obplayer.AudioLog.stop() 40 | if hasattr(obplayer, 'LogUploader'): 41 | obplayer.LogUploader.stop() 42 | -------------------------------------------------------------------------------- /obplayer/audiolog/uploader.py: -------------------------------------------------------------------------------- 1 | import obplayer 2 | import time 3 | import inotify.adapters 4 | import requests 5 | import json 6 | import base64 7 | 8 | class LogUploader(obplayer.ObThread): 9 | def __init__(self): 10 | obplayer.ObThread.__init__(self, "Log Uploader") 11 | self.daemon = True 12 | self.running = True 13 | 14 | def upload_file(self, file_path): 15 | session = requests.session() 16 | #login_data = json.dumps({'username': obplayer.Config.setting('audiolog_upload_user', True), 'password': obplayer.Config.setting('audiolog_upload_password', True)}) 17 | server_url = obplayer.Config.setting('sync_url', True).replace('remote.php', '') 18 | # This means we are logged in. 19 | with open(file_path, 'rb') as file: 20 | req = session.post('{0}/upload.php'.format(server_url), data=file) 21 | if req.status_code == 200: 22 | # The file uploaded to the server. 23 | # It still needs things a like title though. 24 | data = req.json() 25 | #print(data) 26 | if data['media_supported'] == False: 27 | obplayer.Log.log("Audio log uploaded failed due to a media not supported error. Please enable ogg support in the server.", 'audiolog') 28 | return None 29 | log_date = time.strftime("%c", time.localtime()) 30 | metadata = {"media": [{"local_id":"2", "artist": "OBPlayer logs", "title": "Station Audio Log ({0} Local)".format(log_date), "album":"Station audio log", "year": time.strftime("%Y", time.localtime()), "country_id":"231","category_id":"10","language_id":"54","genre_id":"999","comments":"","is_copyright_owner":"0","is_approved":"1","status":"public","dynamic_select":"0","file_id": data['file_id'],"file_key": data['file_key'],"advanced_permissions_users":[],"advanced_permissions_groups":[]}]} 31 | file_data = json.dumps(metadata) 32 | req = session.post('{0}/api.php'.format(server_url), data={'appkey':obplayer.Config.setting('audiolog_upload_appkey', True), 'c':'media', 'a': 'save', 'd': file_data}) 33 | if req.status_code == 200: 34 | obplayer.Log.log("Audio log uploaded at {0}".format(log_date), 'audiolog') 35 | else: 36 | obplayer.Log.log("Got status code {0} from api call for server media save.".format(req.status_code), 'audiolog') 37 | else: 38 | obplayer.Log.log("Got status code {0} from api call for server media upload.".format(req.status_code), 'audiolog') 39 | 40 | def run(self): 41 | i = inotify.adapters.Inotify() 42 | 43 | i.add_watch(obplayer.Config.datadir + '/audiologs') 44 | while self.running: 45 | for event in i.event_gen(yield_nones=False): 46 | (_, type_names, path, filename) = event 47 | 48 | if type_names[0] == 'IN_CLOSE_WRITE': 49 | print("PATH=[{}] FILENAME=[{}] EVENT_TYPES={}".format( 50 | path, filename, type_names)) 51 | self.upload_file(path + '/' + filename) 52 | 53 | def stop(self): 54 | self.running = False 55 | -------------------------------------------------------------------------------- /obplayer/fallback/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright 2012-2015 OpenBroadcaster, Inc. 6 | 7 | This file is part of OpenBroadcaster Player. 8 | 9 | OpenBroadcaster Player is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Affero General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | OpenBroadcaster Player is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Affero General Public License for more details. 18 | 19 | You should have received a copy of the GNU Affero General Public License 20 | along with OpenBroadcaster Player. If not, see . 21 | """ 22 | 23 | from __future__ import absolute_import 24 | 25 | from obplayer.fallback.fallback_player import ObFallbackPlayer 26 | 27 | FallbackPlayer = None 28 | 29 | def init(): 30 | global FallbackPlayer 31 | FallbackPlayer = ObFallbackPlayer() 32 | 33 | def quit(): 34 | pass 35 | 36 | -------------------------------------------------------------------------------- /obplayer/fallback/fallback_player.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright 2012-2015 OpenBroadcaster, Inc. 6 | 7 | This file is part of OpenBroadcaster Player. 8 | 9 | OpenBroadcaster Player is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Affero General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | OpenBroadcaster Player is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Affero General Public License for more details. 18 | 19 | You should have received a copy of the GNU Affero General Public License 20 | along with OpenBroadcaster Player. If not, see . 21 | """ 22 | 23 | import obplayer 24 | 25 | import os 26 | import sys 27 | import time 28 | import magic 29 | import random 30 | import traceback 31 | 32 | import gi 33 | gi.require_version('Gst', '1.0') 34 | gi.require_version('GstPbutils', '1.0') 35 | from gi.repository import GObject, Gst, GstPbutils 36 | 37 | if sys.version.startswith('3'): 38 | unicode = str 39 | 40 | 41 | class ObFallbackPlayer (obplayer.player.ObPlayerController): 42 | 43 | def __init__(self): 44 | self.media = [] 45 | 46 | self.media_types = [] 47 | self.image_types = [] 48 | 49 | self.media_types.append('audio/x-flac') 50 | self.media_types.append('audio/flac') 51 | self.media_types.append('audio/mpeg') 52 | self.media_types.append('audio/ogg') 53 | 54 | # TODO we're always headless new so we never play images or video?? 55 | #if obplayer.Config.headless == False: 56 | self.image_types.append('image/jpeg') 57 | self.image_types.append('image/png') 58 | self.image_types.append('image/svg+xml') 59 | self.media_types.append('application/ogg') 60 | self.media_types.append('video/ogg') 61 | self.media_types.append('video/x-msvideo') 62 | self.media_types.append('video/mp4') 63 | self.media_types.append('video/mpeg') 64 | 65 | self.play_index = 0 66 | self.image_duration = 15.0 67 | 68 | m = magic.open(magic.MAGIC_MIME) 69 | m.load() 70 | 71 | for (dirname, dirnames, filenames) in os.walk(unicode(obplayer.Config.setting('fallback_media'))): 72 | for filename in filenames: 73 | try: 74 | path = os.path.join(dirname, filename) 75 | uri = obplayer.Player.file_uri(path) 76 | filetype = m.file(path.encode('utf-8')).split(';')[0] 77 | 78 | if filetype in self.media_types: 79 | d = GstPbutils.Discoverer() 80 | mediainfo = d.discover_uri(uri) 81 | 82 | media_type = None 83 | for stream in mediainfo.get_video_streams(): 84 | if stream.is_image(): 85 | media_type = 'image' 86 | else: 87 | media_type = 'video' 88 | break 89 | if not media_type and len(mediainfo.get_audio_streams()) > 0: 90 | media_type = 'audio' 91 | 92 | if media_type: 93 | # we discovered some more fallback media, add to our media list. 94 | self.media.append([ uri, filename, media_type, float(mediainfo.get_duration()) / Gst.SECOND ]) 95 | 96 | if filetype in self.image_types: 97 | self.media.append([ uri, filename, 'image', self.image_duration ]) 98 | except: 99 | obplayer.Log.log("exception while loading fallback media: " + dirname + '/' + filename, 'error') 100 | obplayer.Log.log(traceback.format_exc(), 'error') 101 | 102 | # shuffle the list 103 | random.shuffle(self.media) 104 | 105 | self.ctrl = obplayer.Player.create_controller('fallback', priority=25) 106 | self.ctrl.set_request_callback(self.do_player_request) 107 | 108 | # the player is asking us what to play next 109 | def do_player_request(self, ctrl, present_time, media_class): 110 | if len(self.media) == 0: 111 | return False 112 | 113 | if self.play_index >= len(self.media): 114 | self.play_index = 0 115 | random.shuffle(self.media) # shuffle again to create a new order for next time. 116 | 117 | ctrl.add_request( 118 | media_type = unicode(self.media[self.play_index][2]), 119 | uri = unicode(self.media[self.play_index][0]), 120 | duration = self.media[self.play_index][3], 121 | order_num = self.play_index, 122 | artist = u'unknown', 123 | title = unicode(self.media[self.play_index][1]) 124 | ) 125 | 126 | self.play_index = self.play_index + 1 127 | 128 | return True 129 | 130 | 131 | -------------------------------------------------------------------------------- /obplayer/httpadmin/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright 2012-2015 OpenBroadcaster, Inc. 6 | 7 | This file is part of OpenBroadcaster Player. 8 | 9 | OpenBroadcaster Player is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Affero General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | OpenBroadcaster Player is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Affero General Public License for more details. 18 | 19 | You should have received a copy of the GNU Affero General Public License 20 | along with OpenBroadcaster Player. If not, see . 21 | """ 22 | 23 | from __future__ import absolute_import 24 | 25 | from obplayer.httpadmin.httpadmin import obplayer, ObHTTPAdmin 26 | 27 | 28 | class HTTPAdminThread (obplayer.ObThread): 29 | def try_run(self): 30 | obplayer.HTTPAdmin = ObHTTPAdmin() 31 | obplayer.HTTPAdmin.serve_forever() 32 | 33 | def stop(self): 34 | if obplayer.HTTPAdmin: 35 | obplayer.HTTPAdmin.shutdown() 36 | 37 | def init(): 38 | # run our admin web server. 39 | if obplayer.Config.args.disable_http is False: 40 | HTTPAdminThread().start() 41 | 42 | def quit(): 43 | pass 44 | 45 | -------------------------------------------------------------------------------- /obplayer/httpadmin/http/alertdetails.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= obplayer.HTTPAdmin.title %> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | <% alert = obplayer.alerts.Processor.get_alert(py.DATA('id')[0]) %> 13 | <% alert_info = alert.get_first_info(obplayer.alerts.Processor.language_primary) %> 14 | 15 |
16 | 17 |

Details of <%= alert.identifier %>

18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
Identifier:<%= alert.identifier %>
Sender:<%= alert.sender %>
Sent:<%= alert.sent %>
Codes:<%= '
\n'.join(alert.codes) %>
Status:<%= alert.status %>
Message Type:<%= alert.msgtype %>
Scope:<%= alert.scope %>
49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 |
Language:<%= alert_info.language %>
Event:<%= alert_info.event %>
Urgency:<%= alert_info.urgency %>
Severity:<%= alert_info.severity %>
Certainty:<%= alert_info.certainty %>
Expires:<%= alert_info.expires %>
Sender Name:<%= alert_info.sender %>
Headline:<%= alert_info.headline %>
Description:<%= alert_info.description.replace('\n', '
\n') if alert_info.description else '(none)' %>
Instruction:<%= unicode(alert_info.instruction) if alert_info.instruction else '(none)' %>
Parameters:<%= u'
\n'.join([ u"" + unicode(name) + u": " + unicode(val) + u"" for (name, val) in alert_info.parameters ]) %>
Event Codes:<%= u'
\n'.join([ u"" + unicode(name) + u": " + unicode(val) + u"" for (name, val) in alert_info.eventcodes ]) %>
Attached Resources:<%= u'
\n'.join([ unicode(resource.uri) + u" (" + unicode(resource.mimetype) + u": " + unicode(resource.size) + u")" for resource in alert_info.resources ]) %>
104 | 105 |
106 | 107 |

108 | footer_message openbroadcaster.com. 109 | footer_license. 110 |

111 | 112 | 113 | -------------------------------------------------------------------------------- /obplayer/httpadmin/http/extras/images/layers-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openbroadcaster/obplayer/5f18f0df5fe31540014b7175c225b415c8018a50/obplayer/httpadmin/http/extras/images/layers-2x.png -------------------------------------------------------------------------------- /obplayer/httpadmin/http/extras/images/layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openbroadcaster/obplayer/5f18f0df5fe31540014b7175c225b415c8018a50/obplayer/httpadmin/http/extras/images/layers.png -------------------------------------------------------------------------------- /obplayer/httpadmin/http/extras/images/marker-icon-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openbroadcaster/obplayer/5f18f0df5fe31540014b7175c225b415c8018a50/obplayer/httpadmin/http/extras/images/marker-icon-2x.png -------------------------------------------------------------------------------- /obplayer/httpadmin/http/extras/images/marker-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openbroadcaster/obplayer/5f18f0df5fe31540014b7175c225b415c8018a50/obplayer/httpadmin/http/extras/images/marker-icon.png -------------------------------------------------------------------------------- /obplayer/httpadmin/http/extras/images/marker-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openbroadcaster/obplayer/5f18f0df5fe31540014b7175c225b415c8018a50/obplayer/httpadmin/http/extras/images/marker-shadow.png -------------------------------------------------------------------------------- /obplayer/httpadmin/http/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openbroadcaster/obplayer/5f18f0df5fe31540014b7175c225b415c8018a50/obplayer/httpadmin/http/favicon.ico -------------------------------------------------------------------------------- /obplayer/httpadmin/http/logs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= obplayer.HTTPAdmin.title %> 5 | 6 | 7 | 8 | 9 | <% import os %> 10 | 11 | <% 12 | def format_size(bytes): 13 | for unit in ['', 'KB', 'MB', 'GB' ]: 14 | if bytes < 1024.0: 15 | return "%3.2f %s" % (bytes, unit) 16 | end 17 | bytes /= 1024.0 18 | end 19 | end 20 | %> 21 | 22 | 23 | 24 | 25 |
26 | 27 |
28 |

Status Logs

29 | 30 | 31 | <% basedir = obplayer.Config.get_datadir('logs') %> 32 | <% for filename in sorted(os.listdir(basedir), reverse=True): %> 33 | 34 | 35 | 36 | 37 | <% end %> 38 |
<%= filename %><%= format_size(os.path.getsize(os.path.join(basedir, filename))) %>
39 |
40 | 41 |
42 |

Audio Logs

43 | 44 | 45 | <% basedir = obplayer.Config.get_datadir('audiologs') %> 46 | <% for filename in sorted(os.listdir(basedir), reverse=True): %> 47 | 48 | 49 | 50 | 51 | <% end %> 52 |
<%= filename %><%= format_size(os.path.getsize(os.path.join(basedir, filename))) %>
53 |
54 | 55 |
56 |

Off-air Audio Logs

57 | 58 | 59 | <% basedir = obplayer.ObData.get_datadir() + "/offair-audiologs" %> 60 | <% for filename in sorted(os.listdir(basedir), reverse=True): %> 61 | 62 | 63 | 64 | 65 | <% end %> 66 |
<%= filename %><%= format_size(os.path.getsize(os.path.join(basedir, filename))) %>
67 |
68 | 69 |
70 | 71 |

72 | footer3_message openbroadcaster.com. 73 | Affero GPL v3. 74 |

75 | 76 | 77 | -------------------------------------------------------------------------------- /obplayer/httpadmin/http/map.js: -------------------------------------------------------------------------------- 1 | var Devices = L.layerGroup(); 2 | 3 | function init_map() 4 | { 5 | window.map = new L.map('map'); 6 | var OSMBase = L.tileLayer('https://{s}.tiles.mapbox.com/v3/geoprism.h4g8f1k5/{z}/{x}/{y}.png', { maxZoom: 18 }).addTo(map); 7 | var currentLat = $("input[name=location_latitude]").val(); 8 | var currentLng = $("input[name=location_longitude]").val(); 9 | var latlng = L.latLng(currentLat,currentLng); 10 | 11 | Device = L.marker(latlng,{ 12 | draggable: true 13 | }); 14 | Devices.addLayer(Device).addTo(map); 15 | Device.on('dragend', onDrag); 16 | map.setView(latlng,9); 17 | } 18 | 19 | function onDrag(e) 20 | { 21 | map.panTo(e.target.getLatLng()); 22 | $("input[name=location_longitude]").val(e.target.getLatLng().lng.toFixed(5)) 23 | $("input[name=location_latitude]").val(e.target.getLatLng().lat.toFixed(5)) 24 | } 25 | 26 | function updateMap(){ 27 | var newlat = $("input[name=location_latitude]").val(); 28 | var newlng = $("input[name=location_longitude]").val(); 29 | var newLatLng = L.latLng(newlat,newlng); 30 | 31 | Devices.removeLayer(Device); 32 | Device = L.marker(newLatLng,{draggable: true}); 33 | Device.on('dragend', onDrag); 34 | Devices.addLayer(Device).addTo(map); 35 | map.panTo(newLatLng,9); 36 | } 37 | 38 | $(document).ready(function () { 39 | init_map(); 40 | }); 41 | 42 | -------------------------------------------------------------------------------- /obplayer/httpadmin/strings/default/admin_tab.txt: -------------------------------------------------------------------------------- 1 | 2 | Admin Tab 3 | 4 | Admin: Admin 5 | 6 | http_show_sync: Show Sync Tab 7 | http_show_streaming: Show Streaming Tab 8 | http_show_alerts: Show Alerts Tab 9 | http_show_location: Show Location Tab 10 | http_show_liveassist: Show LiveAssist Tab 11 | 12 | show_indigenous_alert_settings: Show Indigenous Alert settings 13 | show_alert_ledin_settings: Show Alert Lead-in settings 14 | show_station_remote_override_settings: Show Station Remote Override settings 15 | show_ssl_settings: Show SSL settings 16 | show_sdr_streaming_logging_settings: Show SDR streaming/logging settings 17 | 18 | aws_access_key_id: AWS access key ID 19 | aws_secret_access_key: AWS secret access key 20 | aws_region_name: AWS region name 21 | 22 | newsfeed_override: enable News feed override (file drop playback) 23 | 24 | delete_and_reset: Delete data.db and Restart 25 | Reset To Defaults and Restart: Reset To Defaults and Restart 26 | Import Settings: Import Settings 27 | Import: Import 28 | Export Settings: Export Settings 29 | Export: Export 30 | Update Player: Update Player 31 | Update: Update 32 | Check: Check 33 | Current Version: Current Version 34 | Latest Version: Latest Version 35 | Already up to date: Already up to date 36 | update_at_3_am: Restart, and update OS at 3 am 37 | 38 | icecast_admin_password: Icecast Admin Password 39 | icecast_source_password: Icecast Source Password 40 | icecast_relay_password: Icecast Relay Password 41 | -------------------------------------------------------------------------------- /obplayer/httpadmin/strings/default/alert_details.txt: -------------------------------------------------------------------------------- 1 | 2 | Alert Details 3 | 4 | Details of: Details of 5 | 6 | Identifier: Identifier 7 | Sender: Sender 8 | Sent: Sent 9 | Codes: Codes 10 | Status: Status 11 | Message Type: Message Type 12 | Scope: Scope 13 | Language: Language 14 | Event: Event 15 | Urgency: Urgency 16 | Severity: Severity 17 | Certainty: Certainty 18 | Expires: Expires 19 | Sender Name: Sender Name 20 | Headline: Headline 21 | Description: Description 22 | Instruction: Instruction 23 | Parameters: Parameters 24 | Event Codes: Event Codes 25 | Attached Resources: Attached Resources 26 | 27 | 28 | -------------------------------------------------------------------------------- /obplayer/httpadmin/strings/default/alerts_tab.txt: -------------------------------------------------------------------------------- 1 | 2 | Alerts Tab 3 | 4 | Emergency Alerts: Emergency Alerts 5 | 6 | alerts_enable: Enable Emergency Alerts 7 | alerts_location_type: Country to receive alerts for 8 | alerts_aws_voices_enable: Enable AWS Polly voices 9 | alerts_play_leadin_enable: Enable Alert Leadin Message 10 | alerts_play_leadin_file: Alerts Leadin file 11 | alerts_geocode: Location Geocode 12 | alerts_geocode_tooltip: A comma-separated list of partial geocodes. The first part of an alert's geocode must match one of the partial geocodes in this list for the alert to be active. See http://www.statcan.gc.ca/eng/subjects/standard/sgc/geography for a list of Canadian geocodes. 13 | alerts_broadcast_message_in_indigenous_languages: Broadcast alerts in indigenous language(s) 14 | alerts_selected_indigenous_languages: Selected indigenous language(s) to broadcast in 15 | alerts_geocode_invalid: Geocodes must be specified as a list of comma-separated numbers. 16 | 17 | english: English 18 | french: French 19 | 20 | alerts_language_primary: Primary Language 21 | alerts_voice_primary: Primary Voice (for TTS) 22 | alerts_language_secondary: Secondary Language 23 | alerts_voice_secondary: Secondary Voice (for TTS) 24 | 25 | alerts_repeat_interval: Alert Repeat Interval (in minutes) 26 | alerts_repeat_interval_tooltip: The time in minutes between each alert cycle (not including the length of the cycle). If a new active alert is received before this time completes, an alert cycle will start 20 seconds later. 27 | 28 | alerts_repeat_times: Plays Per Alert (0 = no limit) 29 | alerts_repeat_times_tooltip: The number of alert cycles in which a given alert will be played before it expires. Setting this to 0 will repeat each alert until that alert's expiration time is reached or the alert is cancelled by the alerting authority. 30 | 31 | alerts_truncate: Truncate Long Alert Messages 32 | alerts_truncate_tooltip: If checked, alerts that are not marked as 'Broadcast Immediately' will be truncated after the first paragraph (after the first blank line). Alerts marked 'Broadcast Immediately' will never be truncated. 33 | 34 | alerts_play_moderates: Play Moderately Severe Alerts 35 | alerts_play_moderates_tooltip: Include alerts that are not marked 'Broadcast Immediately'. Alerts marked 'Broadcast Immediately' will always be played. 36 | 37 | 38 | Advanced Settings: Advanced Settings 39 | 40 | alerts_purge_files: Purge Saved Alerts After 90 days 41 | alerts_purge_files_tooltip: Delete saved alert xml and wav files after 90 days. 42 | 43 | alerts_trigger_serial: Trigger RS-232 DTR on Alerts 44 | alerts_trigger_serial_tooltip: If checked, the serial port (configured below) will be opened when an alert cycle starts, and the DTR signal will be set. The DTR line will be cleared and the serial port closed when the alert cycle has completed. 45 | 46 | alerts_trigger_serial_file: RS-232 Device Filename 47 | alerts_trigger_serial_file_tooltip: The device filename of the serial port on which to set the DTR line to signal when an alert cycle is under way. 48 | alerts_trigger_serial_file_invalid: The RS-232 device filename does not appear to be valid. 49 | 50 | alerts_trigger_streamer: Trigger Icecast Stream on Alerts 51 | alerts_trigger_streamer_tooltip: If checked, the streamer module (configured on the 'Streaming' tab) will be started when an alert cycle starts, and stopped when it completes. Make sure to uncheck the 'Play Stream on Startup' option on the 'Streaming' tab so that it only doesn't automatically start when the program first starts. 52 | 53 | alerts_play_tests: Play Test Alerts 54 | alerts_play_tests_tooltip: Include alerts that are marked as a test. These are not scheduled tests of the alert system, but internal system tests which are not normally played to the public. 55 | 56 | alerts_play_ledin: Play Ledin Message 57 | alerts_play_ledin_tooltip: Play a message at the start of the broadcast of a alert ready message. 58 | 59 | alerts_leadin_delay: Lead-In Delay (in seconds) 60 | alerts_leadin_delay_tooltip: The time in seconds of silence after an alert cycle has started but before the first alert audio is played. 61 | alerts_leadin_delay_invalid: The alert lead-in delay must be 1s or greater. 62 | 63 | alerts_leadout_delay: Lead-Out Delay (in seconds) 64 | alerts_leadout_delay_tooltip: The time in seconds of silence after an alert cycle has completed. 65 | alerts_leadout_delay_invalid: The alert lead-out delay must be 1s or greater. 66 | 67 | alerts_naad_stream1: NAAD Stream #1 URL 68 | alerts_naad_stream1_invalid: The stream #1 URL does not appear to be valid. 69 | 70 | alerts_naad_stream2: NAAD Stream #2 URL 71 | alerts_naad_stream2_invalid: The stream #2 URL does not appear to be valid. 72 | 73 | alerts_naad_archive1: NAAD Archive #1 URL 74 | alerts_naad_archive1_invalid: The archive #1 URL does not appear to be valid. 75 | 76 | alerts_naad_archive2: NAAD Archive #2 URL 77 | alerts_naad_archive2_invalid: The archive #2 URL does not appear to be valid. 78 | 79 | total_broadcast_intrusive_alerts: Total Broadcast Intrusive Alerts 80 | total_advisory_alerts: Total Advisory Alerts 81 | total_self_generated_test_alerts: Total Self Generated Test Alerts 82 | 83 | 84 | Alerting System Tests: Alerting System Tests 85 | 86 | Test Alerts: Test Alerts 87 | Simple Test: Simple Test 88 | Embedded Audio Test: Embedded Audio Test 89 | External Audio Test: External Audio Test 90 | Embedded Audio and Image Test: Embedded Audio and Image Test 91 | 92 | Inject Alert: Inject Alert 93 | 94 | 95 | Active Alerts: Active Alerts 96 | No Active Alerts: No Active Alerts 97 | 98 | Expired Alerts: Expired Alerts 99 | No Expired Alerts: No Expired Alerts 100 | 101 | Last Heartbeat Received: Last Heartbeat Received 102 | none received: none received 103 | Alerts Will Play In: Alerts Will Play In 104 | 105 | Cancel Alerts: Cancel Alerts 106 | 107 | Cancel: Cancel 108 | Sender: Sender 109 | Times Played: Times Played 110 | Headline: Headline 111 | Description: Description 112 | -------------------------------------------------------------------------------- /obplayer/httpadmin/strings/default/common.txt: -------------------------------------------------------------------------------- 1 | Common 2 | Okay: Okay 3 | Save: Save 4 | New: New 5 | Edit: Edit 6 | Delete: Delete 7 | Restore: Restore 8 | Cancel: Cancel 9 | Yes Delete: Yes, Delete 10 | No Cancel: No, Cancel 11 | Clear: Clear 12 | Close: Close 13 | Details: Details 14 | Download: Download 15 | Search: Search 16 | Add: Add 17 | Yes: Yes 18 | No: No 19 | Expand: Expand 20 | Collapse: Collapse 21 | Enabled: Enabled 22 | Disabled: Disabled 23 | 24 | -------------------------------------------------------------------------------- /obplayer/httpadmin/strings/default/http_tab.txt: -------------------------------------------------------------------------------- 1 | 2 | HTTP Tab 3 | 4 | HTTP(S) Admin: HTTP(S) Admin 5 | 6 | http_admin_language: Language 7 | http_admin_secure: Enable SSL 8 | 9 | http_admin_port: Dashboard Port 10 | http_admin_port_invalid: The web admin port is not valid. 11 | 12 | http_admin_sslreq: Require SSL on Connect? 13 | http_admin_sslkey: SSL Key File 14 | http_admin_sslcert: SSL Certificate File 15 | http_admin_sslcert_invalid: To use the web admin with SSL, a valid certiciate file is required. 16 | http_admin_sslca: SSL CA File 17 | 18 | http_admin_username: Admin Username 19 | http_admin_password: Admin Password 20 | http_admin_password_retype: Retype Admin Password 21 | http_readonly_username: Read-Only Username 22 | http_readonly_password: Read-Only Password 23 | http_readonly_allow_restart: Allow Fullscreen/Restart for Read-Only User 24 | http_admin_title: Dashboard Title 25 | 26 | 27 | -------------------------------------------------------------------------------- /obplayer/httpadmin/strings/default/liveassist_tab.txt: -------------------------------------------------------------------------------- 1 | 2 | LiveAssist Tab 3 | 4 | Live Assist: Live Assist 5 | 6 | live_assist_enable: Enable Live Assist 7 | 8 | live_assist_port: Live Assist HTTP Port 9 | live_assist_port_invalid: Live Assist and HTTP Admin cannot use the same port. 10 | 11 | live_assist_mic_enable: Enable Live Assist Microphone 12 | 13 | Microphone Output Settings: Microphone Output Settings 14 | 15 | live_assist_mic_mode: Microphone Output Mode 16 | live_assist_mic_alsa_device: ALSA Device Name 17 | live_assist_mic_jack_name: Jack Port Name 18 | 19 | Monitor Input Settings: Monitor Input Settings 20 | 21 | live_assist_monitor_mode: Monitor Input Mode 22 | live_assist_monitor_alsa_device: ALSA Device Name 23 | live_assist_monitor_jack_name: Jack Port Name 24 | 25 | 26 | -------------------------------------------------------------------------------- /obplayer/httpadmin/strings/default/location_tab.txt: -------------------------------------------------------------------------------- 1 | 2 | Location Tab 3 | 4 | Location Map: Location Map 5 | 6 | Device Location (use decimal degrees): Device Location (use decimal degrees) 7 | 8 | location_enable: Enable Map Display 9 | location_longitude: Longitude 10 | location_latitude_invalid: Invalid latitude coordinate. 11 | location_latitude: Latitude 12 | location_longitude_invalid: Invalid longitude coordinate. 13 | 14 | drag_marker: Drag and drop marker to update coordinates. 15 | drag_map: Drag and drop map to pan. 16 | 17 | -------------------------------------------------------------------------------- /obplayer/httpadmin/strings/default/misc.txt: -------------------------------------------------------------------------------- 1 | 2 | Footer 3 | footer_message1: OpenBroadcaster Player is released under 4 | footer_message2: Downloads and updates available at 5 | footer_message3: Self-help instructions available at 6 | 7 | Miscellaneous 8 | 9 | Restart: Restart 10 | Restarting: Restarting 11 | Fullscreen: Fullscreen 12 | Fullscreen (On): Fullscreen (On) 13 | Fullscreen (Off): Fullscreen (Off) 14 | 15 | Time 16 | seconds: seconds 17 | minutes: minutes 18 | hours: hours 19 | times: times 20 | 21 | Media Type: 22 | audio: audio 23 | video: video 24 | image: image 25 | 26 | Gstreamer Modes 27 | 28 | Auto Detect: Auto Detect 29 | No Output: No Output 30 | No Input: No Input 31 | 32 | ALSA: ALSA 33 | Esound: Esound 34 | JACK: JACK 35 | OSS: OSS 36 | Pulse Audio: Pulse Audio 37 | Shoutcast: Shoutcast 38 | 39 | X11: X11 40 | XVideo: XVideo 41 | OpenGL: OpenGL 42 | EGL/GLES: EGL/GLES 43 | Wayland: Wayland 44 | Coloured ASCII: Coloured ASCII 45 | 46 | 47 | -------------------------------------------------------------------------------- /obplayer/httpadmin/strings/default/outputs_tab.txt: -------------------------------------------------------------------------------- 1 | 2 | Outputs Tab 3 | 4 | Outputs: Outputs 5 | 6 | Audio Output Settings: Audio Output Settings 7 | 8 | audio_out_mode: Audio Output Mode 9 | audio_output_volume: Audio Output Levels 10 | audio_out_alsa_device: ALSA Device Name 11 | audio_out_jack_name: Jack Port Name 12 | audio_out_shout2send_ip_stream_one: Shoutcast IP (Stream one) 13 | audio_out_shout2send_port_stream_one: Shoutcast Port (Stream one) 14 | audio_out_shout2send_mount_stream_one: Shoutcast Mountpoint (Stream one) 15 | audio_out_shout2send_password_stream_one: Shoutcast Password (Stream one) 16 | audio_out_visualization: Enable Audio Visualization 17 | station_override_tooltip: Stream linein to other stations 18 | station_override_monitored_streams: Streams in order of priority to check for override audio. separate entrys using ',' 19 | station_override: Stream linein to other stations 20 | station_override_server_ip: Streaming server ip 21 | station_override_server_port: Streaming server port 22 | station_override_server_password: Streaming server password 23 | station_override_server_mountpoint: Streaming server mountpoint 24 | station_override_server_bitrate: Streaming server bitrate 25 | station_override_enabled: Enable other stations to take over this one. 26 | station_override_monitored_streams_invalid: you must list one, or more streams! 27 | gst_init_callback: Gstreamer Init Callback 28 | audiolog_enable: Enable Audio Logging 29 | audiolog_quality: Audio Logging Quality Levels (Lower is better) 30 | audiolog_samplerate: Audio Logging Samplerate 31 | audiolog_channels: Audio Logging Channels 32 | audiolog_enable_upload: Enable OBServer uploading of logs 33 | audiolog_upload_appkey: Server App Key 34 | offair_audiolog_enable: Enable Off Air Audio Logging 35 | audiolog_purge_files: Purge Audio Logs After 90 days 36 | audiolog_purge_files_tooltip: Delete audio log files after 90 days. 37 | audiolog_bitrate: Audiolog bit rate 38 | offair_audiolog_feq: Radio Station Frequency 39 | offair_audiolog_icecast_ip: Streaming server ip 40 | offair_audiolog_icecast_port: Streaming server port 41 | offair_audiolog_icecast_mountpoint: Streaming server mountpoint 42 | offair_audiolog_icecast_bitrate: Streaming server bitrate 43 | offair_audiolog_icecast_password: Streaming server password 44 | 45 | 46 | Video Output Settings: Video Output Settings 47 | 48 | video_out_enable: Enable Video Output 49 | video_out_mode: Video Output Mode 50 | video_out_resolution: Video Output Resolution 51 | default: default 52 | 53 | 54 | Video Overlay Settings: Video Overlay Settings 55 | 56 | overlay_enable: Enable Video Overlay 57 | 58 | 59 | LED Sign Settings: LED Sign Settings 60 | 61 | led_sign_enable: Enable LED Sign 62 | 63 | led_sign_serial_file: LED Device Filename 64 | led_sign_serial_file_tooltip: The device filename of the serial port of the LED sign to which a message is sent while an alert cycle is under way. 65 | led_sign_timedisplay: Enable LED Time 66 | led_sign_init_message: LED Initialize Message 67 | led_sign_init_message_tooltip: The message to display during initialization of LED sign. 68 | -------------------------------------------------------------------------------- /obplayer/httpadmin/strings/default/responses.txt: -------------------------------------------------------------------------------- 1 | 2 | Responses 3 | 4 | settings-saved-success: Settings saved. 5 | settings-imported-success: Settings imported successfully. Please restart the player for the settings to take effect. 6 | 7 | player-connection-lost: connection to player lost 8 | permissions-error-guest: You don't have permission to do that. You are current logged in as a guest 9 | 10 | passwords-dont-match: The passwords supplied to do match 11 | 12 | updater-disabled-error: Updating is disabled 13 | 14 | streamer_icecast_bitrate_invalid: The icecast bitrate is invalid 15 | 16 | alerts-disabled-error: The emergency alerts module is currently disabled 17 | alerts-invalid-filename: The test alert specified is invalid or does not exist 18 | alerts-inject-success: Test alert injected successfully. 19 | alerts-cancel-success: Alert(s) cancelled successfully. 20 | alerts-replay-error: The one, or more alerts can't be replayed! 21 | 22 | pulse-control-disabled: PulseAudio controls are disabled 23 | 24 | settings-imported-leadin-audio: successfully imported leadin audio 25 | 26 | settings-imported-leadin-audio-error: their was a error importing the leadin audio. 27 | -------------------------------------------------------------------------------- /obplayer/httpadmin/strings/default/sources_tab.txt: -------------------------------------------------------------------------------- 1 | 2 | Sources Tab 3 | 4 | Sources: Sources 5 | 6 | 7 | Fallback Player Settings: Fallback Player Settings 8 | 9 | fallback_enable: Enable Fallback Media Player 10 | fallback_enable_tooltip: If enabled, and the scheduler is unable to played scheduled content, then content from the fallback media directory will be played until scheduled content is available. 11 | 12 | fallback_media: Fallback Media 13 | fallback_media_invalid: The fallback media directory you have specified does not exist. 14 | 15 | 16 | Audio Input Settings: Audio Input Settings 17 | 18 | audio_in_enable: Enable Audio In Source 19 | audio_in_mode: Audio Input Mode 20 | audio_in_alsa_device: ALSA Device Name 21 | audio_in_jack_name: Jack Port Name 22 | 23 | audio_in_disable_on_silence: Disable When Silence Is Detected? 24 | audio_in_prioritize_above_scheduler: Prioritize Above Scheduler? 25 | audio_in_threshold: Silence/Signal Threshold (dB) 26 | audio_in_disable_time: Time Before Disabling Input (s) 27 | audio_in_enable_time: Time Before Enabling Input (s) 28 | audio_in_log: Enable Audio Input Logging 29 | 30 | AoIP Input Settings: AoIP Input Settings 31 | 32 | aoip_in_enable: Enable AoIP Input 33 | aoip_in_uri: AoIP Source URI 34 | aoip_in_uri_tooltip: URI of source audio stream to use. This can either be an rtsp:// uri or an sdp:/// file reference. 35 | 36 | 37 | RTP/Livewire Audio Input Settings: RTP/Livewire Audio Input Settings 38 | 39 | rtp_in_enable: RTP Input Enable 40 | rtp_in_port: RTP Input Port 41 | rtp_in_address: Multicast Address 42 | rtp_in_address_tooltip: The multicast IP address to receive audio data from. Leave blank to receive unicast data. 43 | rtp_in_encoding: Audio Encoding Format 44 | rtp_in_encoding_tooltip: Audio encoding format to use. Opus requires a clock-rate of 48000Hz. 45 | rtp_in_clock_rate: Audio Clock Rate 46 | rtp_in_enable_rtcp: Use RTCP? 47 | rtp_in_enable_rtcp_tooltip: If enabled, this will listen for RTCP synchronization packets on the next port number. (ie. if input port is set to 5004, RTCP will listen on port 5005). 48 | 49 | 50 | Image Display Settings: Image Display Settings 51 | 52 | images_transitions_enable: Enable Image Transitions 53 | images_width: Fixed Width 54 | images_height: Fixed Height 55 | images_framerate: Fixed Framerate 56 | 57 | 58 | Test Signal Settings: Test Signal Settings 59 | 60 | testsignal_enable: Enable Test Signal 61 | 62 | 63 | -------------------------------------------------------------------------------- /obplayer/httpadmin/strings/default/status_tab.txt: -------------------------------------------------------------------------------- 1 | 2 | Status Tab 3 | 4 | Status: Status 5 | 6 | Show: Show 7 | Audio Stream: Audio Stream 8 | Visual Stream: Visual Stream 9 | 10 | Current Time: Current Time 11 | Uptime: Uptime 12 | Show Type: Show Type 13 | Show ID: Show ID 14 | Show Name: Show Name 15 | Show Description: Show Description 16 | Last Updated: Last Update 17 | 18 | Media Type: Media Type 19 | Order #: Order # 20 | Media ID: Media ID 21 | Media Artist: Media Artist 22 | Media Title: Media Title 23 | Media Duration: Media Duration 24 | Scheduled End: Scheduled End 25 | 26 | Logs: Logs 27 | Log Level: Log Level 28 | Normal: Normal 29 | Debug: Debug 30 | Downloads: Downloads 31 | 32 | Status Media Type 33 | 34 | audio: audio 35 | video: video 36 | image: image 37 | 38 | Status Show Type 39 | 40 | standard: standard 41 | advanced: advanced 42 | liveassist: liveassist 43 | 44 | Logs Download: 45 | 46 | Status Logs: Status Logs 47 | Audio Logs: Audio Logs 48 | 49 | -------------------------------------------------------------------------------- /obplayer/httpadmin/strings/default/streamer_tab.txt: -------------------------------------------------------------------------------- 1 | 2 | Streamer Tab 3 | 4 | Streaming: Streaming 5 | 6 | streamer_enable: Enable Streaming 7 | streamer_enable_tooltip: Enables the streaming module. 8 | 9 | streamer_audio_in_mode: Audio Input Mode 10 | streamer_audio_in_mode_tooltip: The type of audio input to use. If using pulseaudio, make sure to select the "Monitor of ..." source for this input in the "Pulse Audio Volume Control" utility. 11 | 12 | streamer_audio_in_alsa_device: ALSA Device Name 13 | streamer_audio_in_jack_name: Jack Port Name 14 | 15 | 16 | Icecast Streaming: Icecast Streaming 17 | 18 | streamer_0_icecast_enable: Enable Icecast Streaming 19 | streamer_0_icecast_enable_tooltip: If enabled, an audio recorder will send audio to an icecast server via http. 20 | 21 | streamer_0_icecast_mode: Icecast (One) Output Mode 22 | streamer_0_icecast_bitrate: Icecast (One) Output Bitrate 23 | streamer_0_icecast_ip: Icecast (One) IP 24 | streamer_0_icecast_port: Icecast (One) Port 25 | streamer_0_icecast_mount: Icecast (One) Mountpoint 26 | streamer_0_icecast_username: Icecast (One) Username 27 | streamer_0_icecast_password: Icecast (One) Password 28 | streamer_0_icecast_streamname: Icecast (One) Stream Name 29 | streamer_0_icecast_description: Icecast (One) Description 30 | streamer_0_icecast_url: Icecast (One) URL 31 | streamer_0_icecast_public: Publicly List Icecast (One) Stream 32 | streamer_0_title_streaming_enable: Enable title streaming 33 | streamer_1_icecast_enable: Enable Icecast Streaming 34 | streamer_1_icecast_enable_tooltip: If enabled, an audio recorder will send audio to an icecast server via http. 35 | streamer_1_icecast_mode: Icecast (Two) Output Mode 36 | streamer_1_icecast_bitrate: Icecast (Two) Output Bitrate 37 | streamer_1_icecast_ip: Icecast (Two) IP 38 | streamer_1_icecast_port: Icecast (Two) Port 39 | streamer_1_icecast_mount: Icecast (Two) Mountpoint 40 | streamer_1_icecast_username: Icecast (Two) Username 41 | streamer_1_icecast_password: Icecast (Two) Password 42 | streamer_1_icecast_streamname: Icecast (Two) Stream Name 43 | streamer_1_icecast_description: Icecast (Two) Description 44 | streamer_1_icecast_url: Icecast (Two) URL 45 | streamer_1_icecast_public: Publicly List Icecast (Two) Stream 46 | streamer_1_title_streaming_enable: Enable title streaming 47 | 48 | streamer_play_on_startup: Play Streams on Startup 49 | streamer_play_on_startup_tooltip: Uncheck this when using the 'trigger icecast stream on alerts' feature on the alerts tab 50 | 51 | 52 | RTSP Server: RTSP Server 53 | 54 | streamer_rtsp_enable: Enable RTSP Server 55 | streamer_rtsp_enable_tooltip: Enable RTSP server which streams the audio output via RTSP/Multicast RTP. Required for output to Livewire/Ravenna devices. 56 | 57 | streamer_rtsp_port: RTSP Server Port 58 | streamer_rtsp_clock_rate: Audio Output Clock Rate 59 | 60 | streamer_rtsp_allow_discovery: Allow RTSP Service Discovery 61 | streamer_rtsp_allow_discovery_tooltip: Publish the RTSP Server via Avahi (mDNS-SD). Required for automatic discovery by Livewire/Ravenna devices. 62 | 63 | 64 | RTP/Livewire Streaming: RTP/Livewire Streaming 65 | 66 | streamer_rtp_enable: Enable RTP Streaming 67 | streamer_rtp_enable_tooltip: Enable RTP streaming to the ip address specified 68 | streamer_rtp_port: RTP Output Port 69 | streamer_rtp_address: Multicast Address 70 | streamer_rtp_address_tooltip: The multicast or unicast IP address to send audio data to. Use 239.192.0.# for Livewire Multicast 71 | streamer_rtp_encoding: Audio Encoding Format 72 | streamer_rtp_encoding_tooltip: Audio encoding format to use. Opus requires a clock-rate of 48000Hz. 73 | streamer_rtp_clock_rate: Audio Clock Rate 74 | streamer_rtp_enable_rtcp: Use RTCP? 75 | streamer_rtp_enable_rtcp_tooltip: If enabled, this will send RTCP synchronization packets on the next port number. (ie. if output port is set to 5004, RTCP will send on port 5005). 76 | 77 | 78 | YouTube Streaming: YouTube Streaming 79 | 80 | streamer_youtube_enable: Enable YouTube Live Streaming 81 | streamer_youtube_enable_tooltip: Enable YouTube Live Streamer. The audio/video output of the player will be sent to YouTube to be streamed lived. 82 | 83 | streamer_youtube_key: YouTube Streaming Key 84 | 85 | streamer_youtube_mode: YouTube Output Mode 86 | -------------------------------------------------------------------------------- /obplayer/httpadmin/strings/default/summary_tab.txt: -------------------------------------------------------------------------------- 1 | 2 | Summary Tab 3 | 4 | Summary: Summary 5 | Current Configuration: Current Configuration 6 | 7 | Version: Version 8 | Local Time: Local Time: 9 | Device Coordinates: Device Coordinates 10 | 11 | Fixed Image Size & Framerate: Fixed Image Size & Framerate 12 | 13 | 14 | -------------------------------------------------------------------------------- /obplayer/httpadmin/strings/default/sync_tab.txt: -------------------------------------------------------------------------------- 1 | 2 | Sync Tab 3 | 4 | Sync/Media: Sync/Media 5 | 6 | scheduler_enable: Enable The Scheduler 7 | scheduler_enable_tooltip: If enabled, the Player will attempt to connect to an OpenBroadcaster Manager Server and download schedules and media for playback. 8 | 9 | sync_device_id: Device ID 10 | sync_device_id_invalid: The device ID is not valid. 11 | 12 | sync_device_password: Device Password 13 | sync_device_password_invalid: A device password is required. 14 | 15 | sync_url: Sync URL 16 | sync_url_invalid: The sync URL does not appear to be valid. 17 | 18 | sync_buffer: Sync Amount (buffer) 19 | sync_buffer_tooltip: The Player will fetch this many hours of schedules from the Server. 20 | sync_buffer_invalid: The sync buffer is not valid. 21 | 22 | sync_showlock: Show Lock-In Time 23 | sync_showlock_tooltip: When downloading schedules, shows that are scheduled to start within this amount of time will be ignored. 24 | sync_showlock_invalid: The show lock is not valid. 25 | 26 | sync_freq: Media Sync Frequency 27 | sync_freq_tooltip: The number of minutes between syncing of schedules and media content. 28 | sync_freq_invalid: The show sync frequency is not valid. 29 | 30 | sync_freq_priority: Priority Alert Sync Frequency 31 | sync_freq_priority_tooltip: The number of minutes between syncing of priority alerts. 32 | sync_freq_priority_invalid: The priority sync frequency is not valid. 33 | 34 | sync_playlog_enable: Enable Playlog Sync 35 | sync_playlog_enable_tooltip: If enabled, a list of the media content played will be synced with the server. 36 | 37 | sync_freq_playlog: Playlog Sync Frequency 38 | sync_freq_playlog_tooltip: The number of minutes between syncing of playlogs. 39 | sync_freq_playlog_invalid: The playlog sync frequency is not valid. 40 | 41 | sync_mode: Media Sync Mode 42 | sync_mode_tooltip: In 'remote' mode, media will be fetched from the server and deleted when no longer needed. In 'backup' mode, a copy of all media downloaded will be kept for future use in 'Local Media'. In 'local' mode, only media in the 'Local Media' directory will be used, and no media will be downloaded from the server. 43 | remote: remote 44 | backup: backup 45 | local: local 46 | 47 | sync_copy_media_to_backup: Backup Downloaded Media 48 | 49 | local_media: Local Media 50 | local_media_invalid: The local media directory you have specified does not exist. 51 | 52 | remote_media: Remote Media Location 53 | 54 | Enable/Disable Scheduler: Enable/Disable Scheduler 55 | Toggle: Toggle 56 | Enabled: Enabled 57 | Disabled: Disabled 58 | -------------------------------------------------------------------------------- /obplayer/linein/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright 2012-2015 OpenBroadcaster, Inc. 6 | 7 | This file is part of OpenBroadcaster Player. 8 | 9 | OpenBroadcaster Player is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Affero General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | OpenBroadcaster Player is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Affero General Public License for more details. 18 | 19 | You should have received a copy of the GNU Affero General Public License 20 | along with OpenBroadcaster Player. If not, see . 21 | """ 22 | 23 | from __future__ import absolute_import 24 | 25 | import obplayer 26 | 27 | def init(): 28 | def linein_request(self, present_time, media_class): 29 | self.add_request(media_type='linein', duration=31536000) # duration = 1 year (ie. indefinitely) 30 | 31 | ctrl = obplayer.Player.create_controller('linein', priority=10, allow_requeue=False) 32 | ctrl.set_request_callback(linein_request) 33 | 34 | def quit(): 35 | pass 36 | 37 | -------------------------------------------------------------------------------- /obplayer/liveassist/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright 2012-2015 OpenBroadcaster, Inc. 6 | 7 | This file is part of OpenBroadcaster Player. 8 | 9 | OpenBroadcaster Player is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Affero General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | OpenBroadcaster Player is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Affero General Public License for more details. 18 | 19 | You should have received a copy of the GNU Affero General Public License 20 | along with OpenBroadcaster Player. If not, see . 21 | """ 22 | 23 | from __future__ import absolute_import 24 | 25 | from obplayer.liveassist.liveassist import * 26 | 27 | LiveAssist = None 28 | 29 | class LiveAssistThread (obplayer.ObThread): 30 | def try_run(self): 31 | obplayer.LiveAssist = ObLiveAssist() 32 | obplayer.LiveAssist.serve_forever() 33 | 34 | def stop(self): 35 | if hasattr(obplayer, 'LiveAssist') and obplayer.LiveAssist: 36 | obplayer.LiveAssist.shutdown() 37 | 38 | def init(): 39 | if not obplayer.Config.setting('scheduler_enable'): 40 | obplayer.Log.log("error starting liveassist. The scheduler must be enabled in order to use the liveassist interface, but it is currently disabled in the settings", 'error') 41 | return 42 | 43 | if obplayer.Config.setting('live_assist_enable'): 44 | LiveAssistThread().start() 45 | 46 | def quit(): 47 | pass 48 | -------------------------------------------------------------------------------- /obplayer/liveassist/http/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | OpenBroadcaster Live Assist 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 26 | 27 |
28 | 29 |
30 |
31 | 32 |
33 |
34 | 35 |
36 |
37 | 38 | 90 | 91 |
92 | 93 |
94 | 95 |
96 | Status 97 | 98 |
99 | 100 |
101 | Controls 102 |       103 |
104 | 105 |
106 | Track Position - () 107 |
108 |
109 | 110 |
111 | 112 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /obplayer/liveassist/http/jquery-ui-css/smoothness/images/animated-overlay.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openbroadcaster/obplayer/5f18f0df5fe31540014b7175c225b415c8018a50/obplayer/liveassist/http/jquery-ui-css/smoothness/images/animated-overlay.gif -------------------------------------------------------------------------------- /obplayer/liveassist/http/jquery-ui-css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openbroadcaster/obplayer/5f18f0df5fe31540014b7175c225b415c8018a50/obplayer/liveassist/http/jquery-ui-css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png -------------------------------------------------------------------------------- /obplayer/liveassist/http/jquery-ui-css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openbroadcaster/obplayer/5f18f0df5fe31540014b7175c225b415c8018a50/obplayer/liveassist/http/jquery-ui-css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png -------------------------------------------------------------------------------- /obplayer/liveassist/http/jquery-ui-css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openbroadcaster/obplayer/5f18f0df5fe31540014b7175c225b415c8018a50/obplayer/liveassist/http/jquery-ui-css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png -------------------------------------------------------------------------------- /obplayer/liveassist/http/jquery-ui-css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openbroadcaster/obplayer/5f18f0df5fe31540014b7175c225b415c8018a50/obplayer/liveassist/http/jquery-ui-css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png -------------------------------------------------------------------------------- /obplayer/liveassist/http/jquery-ui-css/smoothness/images/ui-bg_glass_75_dadada_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openbroadcaster/obplayer/5f18f0df5fe31540014b7175c225b415c8018a50/obplayer/liveassist/http/jquery-ui-css/smoothness/images/ui-bg_glass_75_dadada_1x400.png -------------------------------------------------------------------------------- /obplayer/liveassist/http/jquery-ui-css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openbroadcaster/obplayer/5f18f0df5fe31540014b7175c225b415c8018a50/obplayer/liveassist/http/jquery-ui-css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png -------------------------------------------------------------------------------- /obplayer/liveassist/http/jquery-ui-css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openbroadcaster/obplayer/5f18f0df5fe31540014b7175c225b415c8018a50/obplayer/liveassist/http/jquery-ui-css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png -------------------------------------------------------------------------------- /obplayer/liveassist/http/jquery-ui-css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openbroadcaster/obplayer/5f18f0df5fe31540014b7175c225b415c8018a50/obplayer/liveassist/http/jquery-ui-css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png -------------------------------------------------------------------------------- /obplayer/liveassist/http/jquery-ui-css/smoothness/images/ui-icons_222222_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openbroadcaster/obplayer/5f18f0df5fe31540014b7175c225b415c8018a50/obplayer/liveassist/http/jquery-ui-css/smoothness/images/ui-icons_222222_256x240.png -------------------------------------------------------------------------------- /obplayer/liveassist/http/jquery-ui-css/smoothness/images/ui-icons_2e83ff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openbroadcaster/obplayer/5f18f0df5fe31540014b7175c225b415c8018a50/obplayer/liveassist/http/jquery-ui-css/smoothness/images/ui-icons_2e83ff_256x240.png -------------------------------------------------------------------------------- /obplayer/liveassist/http/jquery-ui-css/smoothness/images/ui-icons_454545_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openbroadcaster/obplayer/5f18f0df5fe31540014b7175c225b415c8018a50/obplayer/liveassist/http/jquery-ui-css/smoothness/images/ui-icons_454545_256x240.png -------------------------------------------------------------------------------- /obplayer/liveassist/http/jquery-ui-css/smoothness/images/ui-icons_888888_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openbroadcaster/obplayer/5f18f0df5fe31540014b7175c225b415c8018a50/obplayer/liveassist/http/jquery-ui-css/smoothness/images/ui-icons_888888_256x240.png -------------------------------------------------------------------------------- /obplayer/liveassist/http/jquery-ui-css/smoothness/images/ui-icons_cd0a0a_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openbroadcaster/obplayer/5f18f0df5fe31540014b7175c225b415c8018a50/obplayer/liveassist/http/jquery-ui-css/smoothness/images/ui-icons_cd0a0a_256x240.png -------------------------------------------------------------------------------- /obplayer/liveassist/http/oldwebrtc.js: -------------------------------------------------------------------------------- 1 | 2 | var configuration = { "iceServers": [{ "url": "stun:stun.l.google.com:19302" }] }; 3 | var pc = new RTCPeerConnection(configuration); 4 | 5 | pc.onicecandidate = function (evt) { 6 | console.log(JSON.stringify({ "candidate": evt.candidate })); 7 | }; 8 | 9 | var pc2 = new RTCPeerConnection(); 10 | 11 | if (navigator.getUserMedia) { 12 | navigator.getUserMedia({ audio: true, video: false }, 13 | function (stream) { 14 | //var video = document.querySelector('video'); 15 | //video.src = window.URL.createObjectURL(stream); 16 | //video.onloadedmetadata = function(e) { 17 | // video.play(); 18 | //}; 19 | 20 | //var audioTracks = stream.getAudioTracks(); 21 | var audio = document.querySelector('#local-audio'); 22 | stream.onended = function() { 23 | console.log('Stream ended'); 24 | }; 25 | window.stream = stream; 26 | audio.srcObject = stream; 27 | 28 | pc.addStream(stream); 29 | pc.addIceCandidate(new RTCIceCandidate({ candidate: "a=candidate:1 1 UDP 2130706431 192.168.1.102 1816 typ host" })); 30 | pc.createOffer(function (desc) { 31 | pc.setLocalDescription(desc); 32 | console.log("Offer"); 33 | console.log(desc.sdp, JSON.stringify(desc)); 34 | 35 | pc2.setRemoteDescription(desc); 36 | pc2.createAnswer(function (desc2) { 37 | console.log("Answer"); 38 | console.log(desc2.sdp, JSON.stringify(desc2)); 39 | pc.setRemoteDescription(new RTCSessionDescription(desc2)); 40 | 41 | $.post('/command/open_stream', { 'sdp': desc.sdp, 'sdpanswer': desc2.sdp }, function () { 42 | console.log("Got it"); 43 | }); 44 | 45 | }, 46 | function (err) { 47 | console.log("The following error occured: " + err.toString()); 48 | }, { mandatory: { OfferToReceiveAudio: true, OfferToReceiveVideo: false } }); 49 | 50 | 51 | }, 52 | function (err) { 53 | console.log("The following error occured: " + err.toString()); 54 | }, { audio: true }); 55 | 56 | 57 | 58 | }, 59 | function (err) { 60 | console.log("The following error occured: " + err.name); 61 | } 62 | ); 63 | } else { 64 | console.log("getUserMedia not supported"); 65 | } 66 | -------------------------------------------------------------------------------- /obplayer/liveassist/http/stream.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | OpenBroadcaster Live Assist 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 23 | 24 | 29 | 30 | 34 | 35 | 36 | 37 | 38 |
39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /obplayer/liveassist/http/svg/next.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 53 | 59 | 60 | -------------------------------------------------------------------------------- /obplayer/liveassist/http/svg/pause.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 53 | 59 | 60 | -------------------------------------------------------------------------------- /obplayer/liveassist/http/svg/play.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 53 | 59 | 60 | -------------------------------------------------------------------------------- /obplayer/liveassist/http/svg/prev.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 53 | 59 | 60 | -------------------------------------------------------------------------------- /obplayer/newsfeed_override/__init__.py: -------------------------------------------------------------------------------- 1 | import obplayer 2 | from .override import * 3 | 4 | def init(): 5 | obplayer.newsfeed_override = Override_Thread() 6 | obplayer.newsfeed_override.start() 7 | 8 | def quit(): 9 | obplayer.newsfeed_override.stop() 10 | -------------------------------------------------------------------------------- /obplayer/newsfeed_override/override.py: -------------------------------------------------------------------------------- 1 | import obplayer 2 | import inotify.adapters 3 | import gi 4 | gi.require_version('Gst', '1.0') 5 | gi.require_version('GstPbutils', '1.0') 6 | from gi.repository import GObject, Gst, GstPbutils 7 | import os 8 | import time 9 | 10 | class Override_Thread(obplayer.ObThread): 11 | def __init__(self): 12 | obplayer.ObThread.__init__(self, 'ObNewsFeedAudioOverride') 13 | self.daemon = True 14 | self.running = False 15 | self.i = inotify.adapters.Inotify() 16 | self.watch_folder = obplayer.Config.datadir + '/news_feed_override' 17 | self.i.add_watch(self.watch_folder) 18 | self.ctrl = obplayer.Player.create_controller('news_feed_override', priority=98, allow_requeue=False) 19 | 20 | def run(self): 21 | self.running = True 22 | # remove any files in the watch folder on startup. 23 | for file in os.listdir(self.watch_folder): 24 | file_path = self.watch_folder + '/' + file 25 | os.remove(file_path) 26 | 27 | while self.running: 28 | for event in self.i.event_gen(yield_nones=False): 29 | (_, type_names, path, filename) = event 30 | if not 'IN_CLOSE_WRITE' in event[1]: 31 | continue 32 | else: 33 | uri = obplayer.Player.file_uri(path, filename) 34 | d = GstPbutils.Discoverer() 35 | mediainfo = d.discover_uri(uri) 36 | duration = mediainfo.get_duration() / float(Gst.SECOND) 37 | obplayer.Log.log("playing news feed media file: {0}".format(filename), 'newsfeed_override') 38 | self.ctrl.add_request(media_type='audio', uri=uri, duration=duration) 39 | time.sleep(duration) 40 | obplayer.Log.log("removing news feed media file: {0}".format(filename), 'newsfeed_override') 41 | try: 42 | os.remove(path + '/' + filename) 43 | except Exception as e: 44 | obplayer.Log.log("an exception occurred while removing news feed media file: {0}".format(filename), 'newsfeed_override') 45 | 46 | 47 | def quit(self): 48 | self.running = False 49 | -------------------------------------------------------------------------------- /obplayer/offair_audiolog/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright 2012-2015 OpenBroadcaster, Inc. 6 | 7 | This file is part of OpenBroadcaster Player. 8 | 9 | OpenBroadcaster Player is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Affero General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | OpenBroadcaster Player is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Affero General Public License for more details. 18 | 19 | You should have received a copy of the GNU Affero General Public License 20 | along with OpenBroadcaster Player. If not, see . 21 | """ 22 | 23 | from __future__ import absolute_import 24 | 25 | import obplayer 26 | 27 | from .audiolog import Oboff_air_AudioLog 28 | 29 | def init(): 30 | obplayer.off_air_AudioLog = Oboff_air_AudioLog() 31 | 32 | def quit(): 33 | # stop the audio logger. 34 | if hasattr(obplayer, 'off_air_AudioLog'): 35 | obplayer.off_air_AudioLog.stop() 36 | -------------------------------------------------------------------------------- /obplayer/offair_audiolog/audiolog.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright 2012-2015 OpenBroadcaster, Inc. 6 | 7 | This file is part of OpenBroadcaster Player. 8 | 9 | OpenBroadcaster Player is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Affero General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | OpenBroadcaster Player is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Affero General Public License for more details. 18 | 19 | You should have received a copy of the GNU Affero General Public License 20 | along with OpenBroadcaster Player. If not, see . 21 | """ 22 | 23 | import obplayer 24 | 25 | import os 26 | import os.path 27 | import time 28 | import datetime 29 | import traceback 30 | 31 | import gi 32 | gi.require_version('Gst', '1.0') 33 | from gi.repository import GObject, Gst 34 | from .recorder import Recorder 35 | 36 | AUDIOLOG_SAMPLE_RATE = '22050' 37 | AUDIOLOG_CHANNELS = '1' 38 | 39 | # class Recorder(threading.Thread): 40 | # def __init__(self, fm_feq, sample_rate): 41 | # obplayer.ObThread.__init__(self, 'Oboff_air_AudioLog-Recorder') 42 | # self.daemon = True 43 | # self.audio_data = [] 44 | # self.recording = False 45 | # self.process = subprocess.Popen(['rtl_fm', '-f', fm_feq, '-m', 'wbfm', '-r', sample_rate], stdout=subprocess.PIPE) 46 | # self._record_audio() 47 | # 48 | # def _record_audio(self): 49 | # self.recording = True 50 | # while self.recording: 51 | # self.audio_data.append(self.process.read()) 52 | # 53 | # def get_audio(self): 54 | # return self.audio_data 55 | # 56 | # def stop(self): 57 | # self.recording = False 58 | 59 | class Oboff_air_AudioLog (object): 60 | def __init__(self): 61 | self.recording = False 62 | self.purge_files = obplayer.Config.setting('audiolog_purge_files') 63 | self.date = time.strftime('%Y-%m-%d-%H') 64 | self.audio_data = [] 65 | self.recorder = None 66 | self.start() 67 | # try: 68 | # self.sdr = RtlSdr() 69 | # except Exception as OSError: 70 | # obplayer.Log.log("Could not start off-air audio log.\n\ 71 | # Make sure your sdr is connected.", 'offair-audiolog') 72 | # self.sdr = None 73 | # if self.sdr != None: 74 | # self.sample_rate = AUDIOLOG_SAMPLE_RATE 75 | # self.fm_feq = obplayer.Config.setting('offair_audiolog_feq') 76 | # self.start() 77 | 78 | def start(self): 79 | if self.recording == False: 80 | self.outfile = obplayer.ObData.get_datadir() + '/offair-audiologs/' + time.strftime('%Y-%m-%d_%H:%M:%S') + '.wav' 81 | self.recorder = Recorder(self.outfile) 82 | self.recorder.start() 83 | #self.recorder.join() 84 | # log that a new recording is being started. 85 | obplayer.Log.log("starting new off-air audio log", 'offair-audiolog') 86 | self.log_rotate() 87 | else: 88 | # log if already recording. 89 | obplayer.Log.log("can't start new off-air audio log because already recording log.", 'offair-audiolog') 90 | 91 | def stop(self): 92 | self.recording = False 93 | self.recorder.stop() 94 | 95 | def log_rotate(self): 96 | if self.date != time.strftime('%Y-%m-%d-%H'): 97 | self.date = time.strftime('%Y-%m-%d-%H') 98 | self.stop() 99 | self.start() 100 | if self.purge_files: 101 | self.log_purge() 102 | GObject.timeout_add(10.0, self.log_rotate) 103 | 104 | def log_purge(self): 105 | basedir = obplayer.ObData.get_datadir() + "/offair-audiologs" 106 | then = datetime.datetime.now() - datetime.timedelta(days=90) 107 | 108 | for filename in os.listdir(basedir): 109 | parts = filename[:10].split('-') 110 | if len(parts) != 3: 111 | continue 112 | filedate = datetime.datetime(int(parts[0]), int(parts[1]), int(parts[2])) 113 | if filedate < then: 114 | obplayer.Log.log("deleting audiolog file " + filename, 'debug') 115 | os.remove(os.path.join(basedir, filename)) 116 | -------------------------------------------------------------------------------- /obplayer/offair_audiolog/recorder.py: -------------------------------------------------------------------------------- 1 | import obplayer 2 | import subprocess 3 | import wave 4 | 5 | class Recorder(obplayer.ObThread): 6 | def __init__(self, output_file): 7 | obplayer.ObThread.__init__(self, 'Oboff_air_AudioLog-Recorder') 8 | self.rtl_process = None 9 | self.ffmpeg = None 10 | self.daemon = True 11 | self.output_file = output_file 12 | self.audio_data = [] 13 | self.recording = False 14 | fm_feq = str(obplayer.Config.setting('offair_audiolog_feq')) 15 | sample_rate = '8000' 16 | icecast_location = str(obplayer.Config.setting('offair_audiolog_icecast_ip')) + ':' + str(obplayer.Config.setting('offair_audiolog_icecast_port')) 17 | icecast_mountpoint = obplayer.Config.setting('offair_audiolog_icecast_mountpoint') 18 | icecast_password = obplayer.Config.setting('offair_audiolog_icecast_password') 19 | icecast_bitrate = obplayer.Config.setting('offair_audiolog_icecast_bitrate') 20 | try: 21 | self.rtl_process = subprocess.Popen(['rtl_fm', '-f', fm_feq + 'M', '-M', 'wbfm', '-r', sample_rate], stdout=subprocess.PIPE, stderr=subprocess.PIPE) 22 | except Exception as e: 23 | obplayer.Log.log("Could not start off-air audio log.\n\ 24 | Make sure your sdr is connected and the required software is installed.", 'offair-audiolog') 25 | self.rtl_process = None 26 | try: 27 | self.ffmpeg = subprocess.Popen(['ffmpeg', '-f', 's16le', '-ar', '8000', '-i', '-', '-acodec', 'libmp3lame', '-ab', icecast_bitrate + 'k', '-ac', '1', '-content_type', 'audio/mpeg', '-f', 'mp3', 28 | 'icecast://source:{0}@{1}/{2}'.format(icecast_password, icecast_location, icecast_mountpoint)], stdin=subprocess.PIPE, stderr=subprocess.PIPE) 29 | if self.ffmpeg.poll() != None: 30 | obplayer.Log.log("Could not start streaming off-air audio log.\n\ 31 | Make sure your sdr is connected, ffmpeg is inbstalled, and that your icecast settings are entered.", 'offair-audiolog') 32 | self.ffmpeg = None 33 | except Exception as e: 34 | obplayer.Log.log("Could not start streaming off-air audio log.\n\ 35 | Make sure your sdr is connected, ffmpeg is inbstalled, and that your icecast settings are entered.", 'offair-audiolog') 36 | self.ffmpeg = None 37 | 38 | def run(self): 39 | if self.rtl_process != None and self.ffmpeg != None: 40 | self._record_audio() 41 | 42 | def _record_audio(self): 43 | self.recording = True 44 | while self.recording: 45 | try: 46 | data = self.rtl_process.stdout.read(1) 47 | if data != b'': 48 | self.audio_data.append(data) 49 | self.ffmpeg.stdin.write(data) 50 | except BrokenPipeError as e: 51 | break 52 | 53 | def get_audio(self): 54 | return b''.join(self.audio_data) 55 | 56 | def stop(self): 57 | self.recording = False 58 | data = self.get_audio() 59 | if self.rtl_process != None: 60 | self.rtl_process.terminate() 61 | if self.ffmpeg != None: 62 | self.ffmpeg.terminate() 63 | if data != b'': 64 | with wave.open(self.output_file, 'wb') as wf: 65 | wf.setnchannels(1) 66 | wf.setsampwidth(2) 67 | wf.setframerate(8000) 68 | wf.writeframes(data) 69 | -------------------------------------------------------------------------------- /obplayer/password_system.py: -------------------------------------------------------------------------------- 1 | import passlib.hash as hashing 2 | 3 | # Builds password hash/salt, and returns the data for storage. 4 | def create_password_hash(password): 5 | return hashing.bcrypt.hash(password) 6 | # Checks if the user provided when hashed 7 | # matches the hash in the db. 8 | def login_check(input_password, db_hash): 9 | return hashing.bcrypt.verify(input_password, db_hash) -------------------------------------------------------------------------------- /obplayer/player/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright 2012-2015 OpenBroadcaster, Inc. 6 | 7 | This file is part of OpenBroadcaster Player. 8 | 9 | OpenBroadcaster Player is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Affero General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | OpenBroadcaster Player is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Affero General Public License for more details. 18 | 19 | You should have received a copy of the GNU Affero General Public License 20 | along with OpenBroadcaster Player. If not, see . 21 | """ 22 | 23 | from __future__ import absolute_import 24 | 25 | import obplayer 26 | 27 | from .control import ObPlayer, ObPlayerController 28 | from .playlog import ObPlaylogData 29 | 30 | def init(): 31 | obplayer.Player = ObPlayer() 32 | obplayer.PlaylogData = ObPlaylogData() 33 | 34 | def quit(): 35 | pass 36 | 37 | -------------------------------------------------------------------------------- /obplayer/player/overlay.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright 2012-2015 OpenBroadcaster, Inc. 6 | 7 | This file is part of OpenBroadcaster Player. 8 | 9 | OpenBroadcaster Player is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Affero General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | OpenBroadcaster Player is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Affero General Public License for more details. 18 | 19 | You should have received a copy of the GNU Affero General Public License 20 | along with OpenBroadcaster Player. If not, see . 21 | """ 22 | 23 | import obplayer 24 | 25 | import gi 26 | gi.require_version('Gtk', '3.0') 27 | gi.require_version('PangoCairo', '1.0') 28 | from gi.repository import GObject, Gtk, Gdk, GdkX11, GdkPixbuf, Pango, PangoCairo 29 | import cairo 30 | 31 | import ctypes 32 | import ctypes.util 33 | import cairo 34 | import sys 35 | import threading 36 | 37 | pycairo_dll = ctypes.pydll.LoadLibrary(cairo._cairo.__file__) 38 | pycairo_dll.PycairoContext_FromContext.restype = ctypes.py_object 39 | pycairo_dll.PycairoContext_FromContext.argtypes = (ctypes.c_void_p, ctypes.py_object, ctypes.py_object) 40 | 41 | cairo_dll = ctypes.pydll.LoadLibrary(ctypes.util.find_library('cairo')) 42 | cairo_dll.cairo_reference.restype = ctypes.c_void_p 43 | cairo_dll.cairo_reference.argtypes = (ctypes.c_void_p,) 44 | 45 | def cairo_context_from_gi(gicr): 46 | #assert isinstance(gicr, GObject.GBoxed) 47 | offset = sys.getsizeof(object()) # size of PyObject_HEAD 48 | # Pull the "boxed" pointer off out and use it as a cairo_t* 49 | cr_ptr = ctypes.c_void_p.from_address(id(gicr) + offset) 50 | cr = pycairo_dll.PycairoContext_FromContext(cr_ptr, cairo.Context, None) 51 | # Add a new ref because the pycairo context will attempt to manage this 52 | cairo_dll.cairo_reference(cr_ptr) 53 | return cr 54 | 55 | 56 | class ObOverlay (object): 57 | def __init__(self): 58 | self.message = None 59 | self.scroll_enable = False 60 | self.scroll_pos = 0.0 61 | self.scroll_wrap = 1.0 62 | self.lock = threading.Lock() 63 | GObject.timeout_add(50, self.overlay_scroll_timer) 64 | 65 | def overlay_scroll_timer(self): 66 | with self.lock: 67 | self.scroll_pos -= 0.015 68 | if self.scroll_pos <= 0.0: 69 | self.scroll_pos = self.scroll_wrap 70 | GObject.timeout_add(50, self.overlay_scroll_timer) 71 | 72 | def set_message(self, msg): 73 | if msg: 74 | self.scroll_enable = True 75 | with self.lock: 76 | if self.message != msg: 77 | self.scroll_pos = 0.05 78 | self.message = msg 79 | else: 80 | self.scroll_enable = False 81 | 82 | def draw_overlay(self, context, width, height): 83 | context = cairo_context_from_gi(context) 84 | 85 | if self.scroll_enable and self.message: 86 | #print str(width) + " x " + str(height) 87 | #context.scale(width, height) 88 | #context.scale(width / 100, height / 100) 89 | #context.scale(100, 100) 90 | #context.set_source_rgb(1, 0, 0) 91 | #context.paint_with_alpha(1) 92 | #context.select_font_face("Helvetica") 93 | #context.set_font_face(None) 94 | #context.set_font_size(0.05) 95 | #context.move_to(0.1, 0.1) 96 | #context.show_text("Hello World") 97 | #context.rectangle(0, height * 0.60, width, 30) 98 | #context.rectangle(0, 0.60, 1, 0.1) 99 | 100 | context.set_source_rgb(1, 0, 0) 101 | context.rectangle(0, 0.55 * height, width, 0.15 * height) 102 | context.fill() 103 | 104 | #context.scale(1.0 / width, 1.0 / height) 105 | #context.translate(0, height * 0.60) 106 | 107 | layout = PangoCairo.create_layout(context) 108 | #font = Pango.FontDescription("Arial " + str(0.090 * height)) 109 | #font.set_family("Sans") 110 | #font.set_size(0.090 * height) 111 | #font.set_size(25) 112 | #font.set_stretch(Pango.Stretch.ULTRA_CONDENSED) 113 | font = Pango.font_description_from_string("Sans Condensed " + str(0.090 * height)) 114 | layout.set_font_description(font) 115 | layout.set_text(self.message, -1) 116 | 117 | context.save() 118 | (layout_width, layout_height) = layout.get_pixel_size() 119 | self.scroll_wrap = 1.0 + (float(layout_width) / float(width)) 120 | pos = (self.scroll_pos * width) - layout_width 121 | context.set_source_rgb(1, 1, 1) 122 | context.translate(pos, 0.55 * height) 123 | PangoCairo.update_layout(context, layout) 124 | PangoCairo.show_layout(context, layout) 125 | context.restore() 126 | 127 | #context.set_line_width(0.1) 128 | #context.move_to(0, 0) 129 | #context.line_to(1, 0) 130 | #context.stroke() 131 | 132 | #pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size("/home/trans/Downloads/kitty.jpg", width, height) 133 | #Gdk.cairo_set_source_pixbuf(context, pixbuf, 0, 0) 134 | #context.stroke() 135 | 136 | -------------------------------------------------------------------------------- /obplayer/player/pipes/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright 2012-2015 OpenBroadcaster, Inc. 6 | This file is part of OpenBroadcaster Player. 7 | OpenBroadcaster Player is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU Affero General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | OpenBroadcaster Player is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU Affero General Public License for more details. 15 | You should have received a copy of the GNU Affero General Public License 16 | along with OpenBroadcaster Player. If not, see . 17 | """ 18 | 19 | from __future__ import absolute_import 20 | 21 | from .base import ObGstPipeline 22 | from .breakbin import ObBreakPipeline 23 | from .decodebin import ObPlayBinPipeline, ObAudioPlayBinPipeline, ObDecodeBinPipeline 24 | from .image import ObImagePipeline 25 | from .linein import ObLineInPipeline 26 | from .rtp import ObRTPInputPipeline 27 | from .rtsp import ObRTSPInputPipeline 28 | #from .rtspa import ObRTSPAInputPipeline 29 | from .sdp import ObSDPInputPipeline 30 | from .testsignal import ObTestSignalPipeline 31 | from .remote_audio import ObRemoteInputPipeline 32 | -------------------------------------------------------------------------------- /obplayer/player/pipes/breakbin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright 2012-2015 OpenBroadcaster, Inc. 6 | 7 | This file is part of OpenBroadcaster Player. 8 | 9 | OpenBroadcaster Player is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Affero General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | OpenBroadcaster Player is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Affero General Public License for more details. 18 | 19 | You should have received a copy of the GNU Affero General Public License 20 | along with OpenBroadcaster Player. If not, see . 21 | """ 22 | 23 | import obplayer 24 | 25 | import os 26 | import traceback 27 | 28 | import gi 29 | gi.require_version('Gst', '1.0') 30 | from gi.repository import GObject, Gst, GstVideo, GstController 31 | 32 | from .base import ObGstPipeline 33 | 34 | 35 | """ 36 | class ObBreakPipeline (ObGstPipeline): 37 | output_caps = [ 'audio' ] 38 | 39 | def __init__(self, name, player): 40 | ObGstPipeline.__init__(self, name) 41 | self.player = player 42 | 43 | def patch(self, mode): 44 | # we don't have to do anything because the output patched to us will just remain disconnected 45 | ObGstPipeline.patch(self, mode) 46 | 47 | def unpatch(self, mode): 48 | # we don't have to do anything because the output patched to us will just remain disconnected 49 | ObGstPipeline.unpatch(self, mode) 50 | """ 51 | 52 | 53 | class ObBreakPipeline (ObGstPipeline): 54 | min_class = [ 'audio' ] 55 | max_class = [ 'audio', 'visual' ] 56 | 57 | def __init__(self, name, player): 58 | ObGstPipeline.__init__(self, name) 59 | self.player = player 60 | self.audiosink = None 61 | self.videosink = None 62 | 63 | self.pipeline = Gst.Pipeline(name) 64 | 65 | self.audiotestsrc = Gst.ElementFactory.make('audiotestsrc', name + '-audiotestsrc') 66 | self.audiotestsrc.set_property('wave', 4) # silence 67 | self.audiotestsrc.set_property('is-live', True) 68 | self.pipeline.add(self.audiotestsrc) 69 | 70 | self.videotestsrc = Gst.ElementFactory.make('videotestsrc', name + '-videotestsrc') 71 | self.videotestsrc.set_property('pattern', 2) # black screen 72 | self.videotestsrc.set_property('is-live', True) 73 | self.pipeline.add(self.videotestsrc) 74 | 75 | self.fakesinks = { } 76 | for output in [ 'audio', 'visual' ]: 77 | self.fakesinks[output] = Gst.ElementFactory.make('fakesink') 78 | #self.add_pad(Gst.GhostPad.new('src_' + output, self.audiotestsrc.get_static_pad('src'))) 79 | 80 | self.set_property('audio-sink', self.fakesinks['audio']) 81 | self.set_property('video-sink', self.fakesinks['visual']) 82 | 83 | self.register_signals() 84 | 85 | def set_property(self, property, value): 86 | if property == 'audio-sink': 87 | if self.audiosink: 88 | self.pipeline.remove(self.audiosink) 89 | self.audiosink = value 90 | if self.audiosink: 91 | self.pipeline.add(self.audiosink) 92 | self.audiotestsrc.link(self.audiosink) 93 | elif property == 'video-sink': 94 | if self.videosink: 95 | self.pipeline.remove(self.videosink) 96 | self.videosink = value 97 | if self.videosink: 98 | self.pipeline.add(self.videosink) 99 | self.videotestsrc.link(self.videosink) 100 | 101 | def patch(self, mode): 102 | self.wait_state(Gst.State.NULL) 103 | if 'audio' in mode: 104 | self.set_property('audio-sink', self.player.outputs['audio'].get_bin()) 105 | if 'visual' in mode: 106 | self.set_property('video-sink', self.player.outputs['visual'].get_bin()) 107 | ObGstPipeline.patch(self, mode) 108 | self.wait_state(Gst.State.PLAYING) 109 | if obplayer.Config.setting('gst_init_callback'): 110 | os.system(obplayer.Config.setting('gst_init_callback')) 111 | 112 | def unpatch(self, mode): 113 | self.wait_state(Gst.State.NULL) 114 | if 'audio' in mode: 115 | self.set_property('audio-sink', self.fakesinks['audio']) 116 | if 'visual' in mode: 117 | self.set_property('video-sink', self.fakesinks['visual']) 118 | ObGstPipeline.unpatch(self, mode) 119 | if len(self.mode) > 0: 120 | self.wait_state(Gst.State.PLAYING) 121 | if obplayer.Config.setting('gst_init_callback'): 122 | os.system(obplayer.Config.setting('gst_init_callback')) 123 | 124 | 125 | -------------------------------------------------------------------------------- /obplayer/player/pipes/sdp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright 2012-2015 OpenBroadcaster, Inc. 6 | 7 | This file is part of OpenBroadcaster Player. 8 | 9 | OpenBroadcaster Player is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Affero General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | OpenBroadcaster Player is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Affero General Public License for more details. 18 | 19 | You should have received a copy of the GNU Affero General Public License 20 | along with OpenBroadcaster Player. If not, see . 21 | """ 22 | 23 | import obplayer 24 | 25 | import os 26 | import traceback 27 | 28 | import gi 29 | gi.require_version('Gst', '1.0') 30 | gi.require_version('GstSdp', '1.0') 31 | from gi.repository import GObject, Gst, GstVideo, GstSdp 32 | 33 | from .base import ObGstPipeline 34 | 35 | 36 | class ObSDPInputPipeline (ObGstPipeline): 37 | min_class = [ 'audio' ] 38 | max_class = [ 'audio' ] 39 | 40 | def __init__(self, name, player): 41 | ObGstPipeline.__init__(self, name) 42 | self.player = player 43 | 44 | self.pipeline = Gst.Pipeline(name) 45 | self.elements = [ ] 46 | 47 | self.filesrc = Gst.ElementFactory.make('filesrc', name + '-filesrc') 48 | self.pipeline.add(self.filesrc) 49 | 50 | self.sdpdemux = Gst.ElementFactory.make('sdpdemux', name + '-sdpdemux') 51 | #self.sdpdemux.set_property('debug', True) 52 | self.pipeline.add(self.sdpdemux) 53 | self.filesrc.link(self.sdpdemux) 54 | 55 | def sdpdemux_pad_added(obj, pad): 56 | #print("Pad added " + str(pad)) 57 | #caps = pad.get_current_caps() 58 | pad.link(self.decodebin.get_static_pad('sink')) 59 | self.sdpdemux.connect('pad-added', sdpdemux_pad_added) 60 | 61 | self.decodebin = Gst.ElementFactory.make('decodebin', name + '-decodebin') 62 | self.pipeline.add(self.decodebin) 63 | 64 | def decodebin_pad_added(obj, pad): 65 | caps = pad.get_current_caps().to_string() 66 | #print(caps, pad.is_linked()) 67 | 68 | if caps.startswith('audio'): 69 | pad.link(self.audioconvert.get_static_pad('sink')) 70 | else: 71 | print("Fake sink thing that we don't want") 72 | fakesink = Gst.ElementFactory.make('fakesink') 73 | self.pipeline.add(fakesink) 74 | pad.link(fakesink.get_static_pad('sink')) 75 | 76 | #for p in self.decodebin.iterate_pads(): 77 | # print("Pad: ", p, p.is_linked()) 78 | self.decodebin.connect('pad-added', decodebin_pad_added) 79 | 80 | self.audioconvert = Gst.ElementFactory.make('audioconvert', name + '-convert') 81 | self.pipeline.add(self.audioconvert) 82 | 83 | self.queue = Gst.ElementFactory.make('queue2', name + '-queue') 84 | self.pipeline.add(self.queue) 85 | self.audioconvert.link(self.queue) 86 | 87 | 88 | self.audiosink = None 89 | self.fakesink = Gst.ElementFactory.make('fakesink') 90 | self.set_property('audio-sink', self.fakesink) 91 | 92 | self.register_signals() 93 | #self.bus.connect("message", self.message_handler_rtp) 94 | #self.bus.add_signal_watch() 95 | 96 | def start(self): 97 | # We start the pipe without waiting because it wont enter the playing state until the transmitting end is connected 98 | self.pipeline.set_state(Gst.State.PLAYING) 99 | 100 | def set_property(self, property, value): 101 | if property == 'audio-sink': 102 | if self.audiosink: 103 | self.queue.unlink(self.audiosink) 104 | self.pipeline.remove(self.audiosink) 105 | self.audiosink = value 106 | if self.audiosink: 107 | self.pipeline.add(self.audiosink) 108 | self.queue.link(self.audiosink) 109 | 110 | def patch(self, mode): 111 | self.wait_state(Gst.State.NULL) 112 | if 'audio' in mode: 113 | self.set_property('audio-sink', self.player.outputs['audio'].get_bin()) 114 | ObGstPipeline.patch(self, mode) 115 | 116 | #self.wait_state(Gst.State.PLAYING) 117 | self.pipeline.set_state(Gst.State.PLAYING) 118 | if obplayer.Config.setting('gst_init_callback'): 119 | os.system(obplayer.Config.setting('gst_init_callback')) 120 | 121 | def unpatch(self, mode): 122 | self.wait_state(Gst.State.NULL) 123 | if 'audio' in mode: 124 | self.set_property('audio-sink', self.fakesink) 125 | ObGstPipeline.unpatch(self, mode) 126 | if len(self.mode) > 0: 127 | #self.wait_state(Gst.State.PLAYING) 128 | self.pipeline.set_state(Gst.State.PLAYING) 129 | if obplayer.Config.setting('gst_init_callback'): 130 | os.system(obplayer.Config.setting('gst_init_callback')) 131 | 132 | def set_request(self, req): 133 | self.start_time = req['start_time'] 134 | self.filesrc.set_property('location', req['uri'][7:] if req['uri'].startswith('file://') else req['uri']) 135 | 136 | def message_handler_rtp(self, bus, message): 137 | if message.type == Gst.MessageType.ERROR: 138 | obplayer.Log.log("attempting to restart pipeline", 'info') 139 | GObject.timeout_add(1.0, self.restart_pipeline) 140 | 141 | def restart_pipeline(self): 142 | self.wait_state(Gst.State.NULL) 143 | self.wait_state(Gst.State.PLAYING) 144 | 145 | 146 | -------------------------------------------------------------------------------- /obplayer/player/pipes/testsignal.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright 2012-2015 OpenBroadcaster, Inc. 6 | 7 | This file is part of OpenBroadcaster Player. 8 | 9 | OpenBroadcaster Player is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Affero General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | OpenBroadcaster Player is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Affero General Public License for more details. 18 | 19 | You should have received a copy of the GNU Affero General Public License 20 | along with OpenBroadcaster Player. If not, see . 21 | """ 22 | 23 | import obplayer 24 | 25 | import os 26 | import traceback 27 | 28 | import gi 29 | gi.require_version('Gst', '1.0') 30 | from gi.repository import GObject, Gst, GstVideo, GstController 31 | 32 | from .base import ObGstPipeline 33 | 34 | 35 | class ObTestSignalPipeline (ObGstPipeline): 36 | min_class = [ 'audio', 'visual' ] 37 | max_class = [ 'audio', 'visual' ] 38 | 39 | def __init__(self, name, player): 40 | ObGstPipeline.__init__(self, name) 41 | self.player = player 42 | 43 | self.pipeline = Gst.Pipeline(name) 44 | 45 | self.audiotestsrc = Gst.ElementFactory.make('audiotestsrc', name + '-audiotestsrc') 46 | self.audiotestsrc.set_property('volume', 0.2) 47 | self.audiotestsrc.set_property('is-live', True) 48 | self.pipeline.add(self.audiotestsrc) 49 | 50 | self.videotestsrc = Gst.ElementFactory.make('videotestsrc', name + '-videotestsrc') 51 | self.videotestsrc.set_property('is-live', True) 52 | self.pipeline.add(self.videotestsrc) 53 | 54 | self.audiosink = None 55 | self.videosink = None 56 | 57 | self.fakesinks = { } 58 | for output in self.max_class: 59 | self.fakesinks[output] = Gst.ElementFactory.make('fakesink') 60 | 61 | self.set_property('audio-sink', self.fakesinks['audio']) 62 | self.set_property('video-sink', self.fakesinks['visual']) 63 | 64 | self.register_signals() 65 | 66 | def set_property(self, property, value): 67 | if property == 'audio-sink': 68 | if self.audiosink: 69 | self.pipeline.remove(self.audiosink) 70 | self.audiosink = value 71 | if self.audiosink: 72 | self.pipeline.add(self.audiosink) 73 | self.audiotestsrc.link(self.audiosink) 74 | elif property == 'video-sink': 75 | if self.videosink: 76 | self.pipeline.remove(self.videosink) 77 | self.videosink = value 78 | if self.videosink: 79 | self.pipeline.add(self.videosink) 80 | self.videotestsrc.link(self.videosink) 81 | 82 | def patch(self, mode): 83 | self.wait_state(Gst.State.NULL) 84 | if 'audio' in mode: 85 | self.set_property('audio-sink', self.player.outputs['audio'].get_bin()) 86 | if 'visual' in mode: 87 | self.set_property('video-sink', self.player.outputs['visual'].get_bin()) 88 | ObGstPipeline.patch(self, mode) 89 | self.wait_state(Gst.State.PLAYING) 90 | if obplayer.Config.setting('gst_init_callback'): 91 | os.system(obplayer.Config.setting('gst_init_callback')) 92 | 93 | def unpatch(self, mode): 94 | self.wait_state(Gst.State.NULL) 95 | if 'audio' in mode: 96 | self.set_property('audio-sink', self.fakesinks['audio']) 97 | if 'visual' in mode: 98 | self.set_property('video-sink', self.fakesinks['visual']) 99 | ObGstPipeline.unpatch(self, mode) 100 | if len(self.mode) > 0: 101 | self.wait_state(Gst.State.PLAYING) 102 | if obplayer.Config.setting('gst_init_callback'): 103 | os.system(obplayer.Config.setting('gst_init_callback')) 104 | 105 | 106 | -------------------------------------------------------------------------------- /obplayer/player/playlog.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright 2012-2015 OpenBroadcaster, Inc. 6 | 7 | This file is part of OpenBroadcaster Player. 8 | 9 | OpenBroadcaster Player is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Affero General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | OpenBroadcaster Player is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Affero General Public License for more details. 18 | 19 | You should have received a copy of the GNU Affero General Public License 20 | along with OpenBroadcaster Player. If not, see . 21 | """ 22 | 23 | import obplayer 24 | 25 | import time 26 | 27 | 28 | class ObPlaylogData (obplayer.ObData): 29 | 30 | def __init__(self): 31 | obplayer.ObData.__init__(self) 32 | self.db = self.open_db(self.datadir + '/playlog.db') 33 | 34 | if not self.table_exists('playlog'): 35 | self.execute("CREATE TABLE playlog (id INTEGER PRIMARY KEY, media_id NUMERIC, artist TEXT, title TEXT, datetime NUMERIC, context TEXT, emerg_id NUMERIC, notes TEXT)") 36 | 37 | # 38 | # Add entry to play log. 39 | # 40 | # media_id : id of media being played (to match web app database) 41 | # artist : name of artist being played (in case information is lost in web app db) 42 | # title : title of media being played (in case information is lost in web app db) 43 | # datetime : unix timestamp of play start time (UTC/GMT) 44 | # context : what is the context of this media being played (should be 'show' or 'emerg') 45 | # emerg_id : if this is an priority broadcast, what is the priority broadcast id? 46 | # notes : any misc notes (in particular, offset if play is resumed part-way through). 47 | # 48 | def add_entry(self, media_id, artist, title, datetime, context, notes=''): 49 | if not obplayer.Config.setting('sync_playlog_enable'): 50 | return 51 | 52 | # TODO this is a hack until we can change things server-side 53 | if context == 'alerts': 54 | context = 'emerg' 55 | elif context in [ 'scheduler', 'linein' ]: 56 | context = 'show' 57 | else: 58 | context = 'fallback' 59 | 60 | self.execute("INSERT INTO playlog VALUES (null, ?, ?, ?, ?, ?, ?, ?)", (media_id, artist, title, datetime, context, str(0), notes)) 61 | return self.db.last_insert_rowid() 62 | 63 | # 64 | # Get playlog from given timestamp (used for syncing with web app database) 65 | # 66 | def get_entries_since(self, timestamp): 67 | return self.query("SELECT id,media_id,artist,title,datetime,context,emerg_id,notes from playlog WHERE datetime > " + str(timestamp)) 68 | 69 | # 70 | # Remove playlog entries since ID (used after a successful sync with web app database) 71 | # 72 | def remove_entries_since(self, entryid): 73 | self.execute("DELETE from playlog WHERE id <= " + str(entryid)) 74 | return True 75 | 76 | 77 | -------------------------------------------------------------------------------- /obplayer/pulse/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright 2012-2015 OpenBroadcaster, Inc. 6 | 7 | This file is part of OpenBroadcaster Player. 8 | 9 | OpenBroadcaster Player is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Affero General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | OpenBroadcaster Player is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Affero General Public License for more details. 18 | 19 | You should have received a copy of the GNU Affero General Public License 20 | along with OpenBroadcaster Player. If not, see . 21 | """ 22 | 23 | from __future__ import absolute_import 24 | 25 | import obplayer 26 | 27 | pulse = None 28 | 29 | def init(): 30 | global pulsectl, pulse 31 | 32 | try: 33 | import pulsectl 34 | except ImportError: 35 | obplayer.Log.log("missing python package: pulsectl. PulseAudio controls will be disabled.", 'error') 36 | return 37 | 38 | try: 39 | pulse = pulsectl.Pulse('obplayer-pulsectl') 40 | except: 41 | pulse = None 42 | obplayer.Log.log("failed to connect to PulseAudio server. PulseAudio controls will be disabled.", 'error') 43 | return 44 | 45 | def quit(): 46 | pass 47 | 48 | def is_loaded(): 49 | if pulse != None: 50 | return True 51 | return False 52 | 53 | 54 | def sink_info(index): 55 | return pulse.sink_info(index) 56 | 57 | def sink_list(): 58 | return pulse.sink_list() 59 | 60 | def sink_input_list(): 61 | return pulse.sink_input_list() 62 | 63 | def source_info(index): 64 | return pulse.source_info(index) 65 | 66 | def source_list(): 67 | return pulse.source_list() 68 | 69 | def source_output_list(): 70 | return pulse.source_output_list() 71 | 72 | def _find_item(appname, ilist): 73 | for item in ilist: 74 | if 'application.name' in item.proplist: 75 | if item.proplist['application.name'] == appname: 76 | return item 77 | else: 78 | if item.name == appname: 79 | return item 80 | return None 81 | 82 | def change_volume(name, volume): 83 | if name.startswith('pulse_sink_'): 84 | sink = _find_item(name[11:], pulse.sink_input_list()) 85 | if sink: 86 | pulse.volume_set_all_chans(sink, float(volume) / 100.0) 87 | return sink.volume.values[0] * 100 88 | elif name.startswith('pulse_source_'): 89 | source = _find_item(name[13:], pulse.source_output_list()) 90 | if source: 91 | pulse.volume_set_all_chans(source, float(volume) / 100.0) 92 | return source.volume.values[0] * 100 93 | 94 | def mute(name): 95 | if name.startswith('pulse_sink_'): 96 | sink = _find_item(name[11:], pulse.sink_input_list()) 97 | if sink: 98 | mute = not sink.mute 99 | pulse.sink_input_mute(sink.index, mute) 100 | return mute 101 | 102 | elif name.startswith('pulse_source_'): 103 | source = _find_item(name[13:], pulse.source_output_list()) 104 | if source: 105 | mute = not source.mute 106 | pulse.source_output_mute(source.index, mute) 107 | return mute 108 | 109 | def select_output(name, s_index): 110 | if name.startswith('pulse_sink_select_'): 111 | sink = _find_item(name[18:], pulse.sink_input_list()) 112 | if sink: 113 | pulse.sink_input_move(sink.index, int(s_index)) 114 | elif name.startswith('pulse_source_select_'): 115 | source = _find_item(name[20:], pulse.source_output_list()) 116 | if source: 117 | pulse.source_output_move(source.index, int(s_index)) 118 | 119 | -------------------------------------------------------------------------------- /obplayer/pulse/views/sinks.pyhtml: -------------------------------------------------------------------------------- 1 | 2 |
3 |
PulseAudio Sinks
4 | 5 | 6 | 7 | <% sinks = obplayer.pulse.sink_input_list() %> 8 | <% if len(sinks) > 0: %> 9 | <% for sink in sinks: %> 10 | <% link = obplayer.pulse.sink_info(sink.sink) %> 11 | <% appname = sink.proplist['application.name'] if 'application.name' in sink.proplist else sink.name %> 12 | 13 | 14 | 15 | 16 | 21 | 22 | <% end %> 23 | <% else: %> 24 | 25 | <% end %> 26 | 27 |
<%= appname + " (" + sink.name + ")" %>
No PulseAudio Sinks Present
28 | 29 |
30 | 31 | -------------------------------------------------------------------------------- /obplayer/pulse/views/sources.pyhtml: -------------------------------------------------------------------------------- 1 | 2 |
3 |
PulseAudio Sources
4 | 5 | 6 | 7 | <% sources = [ source for source in obplayer.pulse.source_output_list() if 'application.id' not in source.proplist or not source.proplist['application.id'].startswith("org.PulseAudio.") ] %> 8 | <% if len(sources) > 0: %> 9 | <% for source in sources: %> 10 | <% link = obplayer.pulse.source_info(source.source) %> 11 | <% appname = source.proplist['application.name'] if 'application.name' in source.proplist else source.name %> 12 | 13 | 14 | 15 | 16 | 21 | 22 | <% end %> 23 | <% else: %> 24 | 25 | <% end %> 26 | 27 |
<%= appname + " (" + source.name + ")" %>
No PulseAudio Sources Present
28 | 29 |
30 | 31 | -------------------------------------------------------------------------------- /obplayer/rtpin/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright 2012-2015 OpenBroadcaster, Inc. 6 | 7 | This file is part of OpenBroadcaster Player. 8 | 9 | OpenBroadcaster Player is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Affero General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | OpenBroadcaster Player is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Affero General Public License for more details. 18 | 19 | You should have received a copy of the GNU Affero General Public License 20 | along with OpenBroadcaster Player. If not, see . 21 | """ 22 | 23 | from __future__ import absolute_import 24 | 25 | import obplayer 26 | 27 | def init(): 28 | def rtp_in_request(self, present_time, media_class): 29 | #uri = obplayer.Config.setting('aoip_in_uri') 30 | self.add_request(media_type='rtp', duration=31536000) # duration = 1 year (ie. indefinitely) 31 | 32 | ctrl = obplayer.Player.create_controller('rtpin', priority=15, allow_requeue=False) 33 | ctrl.set_request_callback(rtp_in_request) 34 | 35 | def quit(): 36 | pass 37 | 38 | -------------------------------------------------------------------------------- /obplayer/scheduler/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright 2012-2015 OpenBroadcaster, Inc. 6 | 7 | This file is part of OpenBroadcaster Player. 8 | 9 | OpenBroadcaster Player is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Affero General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | OpenBroadcaster Player is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Affero General Public License for more details. 18 | 19 | You should have received a copy of the GNU Affero General Public License 20 | along with OpenBroadcaster Player. If not, see . 21 | """ 22 | 23 | from __future__ import absolute_import 24 | 25 | import obplayer 26 | 27 | from .scheduler import ObScheduler 28 | from .sync import ObSync, VersionUpdateThread, SyncShowsThread, SyncEmergThread, SyncMediaThread, SyncPlaylogThread, Sync_Alert_Media_Thread 29 | from .priority import ObPriorityBroadcaster 30 | from .data import ObRemoteData 31 | 32 | #Sync = None 33 | #Scheduler = None 34 | 35 | def init(): 36 | #global Sync, Scheduler 37 | 38 | obplayer.Sync = ObSync() 39 | obplayer.Scheduler = ObScheduler() 40 | obplayer.PriorityBroadcaster = ObPriorityBroadcaster() 41 | obplayer.RemoteData = ObRemoteData() 42 | 43 | # reset show/show_media tables, priority tables 44 | if obplayer.Config.args.reset: 45 | obplayer.Log.log('resetting show, media, and priority data', 'data') 46 | obplayer.RemoteData.empty_table('shows') 47 | obplayer.RemoteData.empty_table('shows_media') 48 | obplayer.RemoteData.empty_table('groups') 49 | obplayer.RemoteData.empty_table('group_items') 50 | obplayer.RemoteData.empty_table('priority_broadcasts') 51 | obplayer.RemoteData.empty_table('alert_media') 52 | 53 | # report the player version number to the server if possible 54 | VersionUpdateThread().start() 55 | 56 | # if resetting the databases, run our initial sync. otherwise skip and setup other sync interval timers. 57 | if obplayer.Config.args.reset: 58 | obplayer.Sync.sync_shows(True) 59 | obplayer.Sync.sync_priority_broadcasts() 60 | obplayer.Sync.sync_media() 61 | if obplayer.Config.setting('alerts_broadcast_message_in_indigenous_languages'): 62 | obplayer.Sync.sync_alert_media() 63 | 64 | # Start sync threads 65 | SyncShowsThread().start() 66 | SyncEmergThread().start() 67 | SyncMediaThread().start() 68 | SyncPlaylogThread().start() 69 | if obplayer.Config.setting('alerts_broadcast_message_in_indigenous_languages'): 70 | Sync_Alert_Media_Thread().start() 71 | 72 | def quit(): 73 | # backup our main db to disk. 74 | if hasattr(obplayer, 'RemoteData') and obplayer.Main.exit_code == 0: 75 | obplayer.RemoteData.backup() 76 | -------------------------------------------------------------------------------- /obplayer/scheduler/priority.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright 2012-2015 OpenBroadcaster, Inc. 6 | 7 | This file is part of OpenBroadcaster Player. 8 | 9 | OpenBroadcaster Player is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Affero General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | OpenBroadcaster Player is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Affero General Public License for more details. 18 | 19 | You should have received a copy of the GNU Affero General Public License 20 | along with OpenBroadcaster Player. If not, see . 21 | """ 22 | 23 | import obplayer 24 | 25 | import time 26 | 27 | 28 | class ObPriorityBroadcaster: 29 | def __init__(self): 30 | self.ctrl = obplayer.Player.create_controller('priority', priority=75, default_play_mode='overlap', allow_overlay=True) 31 | #self.ctrl.set_request_callback(self.do_player_request) 32 | #self.ctrl.set_update_callback(self.do_player_update) 33 | #self.ctrl.set_next_update(0) 34 | 35 | def do_player_update(self, ctrl, present_time, media_class): 36 | pass 37 | 38 | def check_update(self): 39 | present_time = time.time() 40 | 41 | # run through priority broadcasts and play if it's time. don't play while syncing priority since it might be changing data or downloading new data. 42 | if obplayer.RemoteData.priority_broadcasts != False and obplayer.Sync.priority_sync_running == False: 43 | for (bindex, broadcast) in obplayer.RemoteData.priority_broadcasts.items(): 44 | if broadcast['next_play'] <= present_time: 45 | 46 | if obplayer.Sync.check_media(broadcast): 47 | 48 | obplayer.Log.log('play priority broadcast', 'priority') 49 | 50 | self.ctrl.add_request( 51 | media_type = broadcast['media_type'], 52 | uri = obplayer.Sync.media_uri(broadcast['file_location'], broadcast['filename']), 53 | media_id = broadcast['media_id'], 54 | artist = broadcast['artist'], 55 | title = broadcast['title'], 56 | duration = broadcast['duration'] + 2 57 | ) 58 | 59 | play_time = self.ctrl.get_requests_endtime() 60 | obplayer.RemoteData.priority_broadcasts[bindex]['next_play'] = play_time + broadcast['duration'] + broadcast['frequency'] 61 | obplayer.RemoteData.priority_broadcasts[bindex]['last_play'] = play_time 62 | 63 | # we set this so the show will resume. 64 | #self.show_update_time = present_time 65 | #break 66 | 67 | -------------------------------------------------------------------------------- /obplayer/streamer/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright 2012-2015 OpenBroadcaster, Inc. 6 | 7 | This file is part of OpenBroadcaster Player. 8 | 9 | OpenBroadcaster Player is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Affero General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | OpenBroadcaster Player is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Affero General Public License for more details. 18 | 19 | You should have received a copy of the GNU Affero General Public License 20 | along with OpenBroadcaster Player. If not, see . 21 | """ 22 | 23 | from __future__ import absolute_import 24 | 25 | import obplayer 26 | 27 | import gi 28 | from gi.repository import GObject 29 | 30 | 31 | def init(): 32 | obplayer.Streamer_stream_1 = None 33 | obplayer.Streamer_stream_2 = None 34 | obplayer.RTSPStreamer = None 35 | obplayer.RTPStreamer = None 36 | obplayer.YoutubeStreamer = None 37 | 38 | from .icecast import ObIcecastStreamer 39 | def delaystart(): 40 | # Start stream one 41 | #print(obplayer.Config.setting('streamer_0_icecast_port')) 42 | obplayer.Streamer_stream_1 = ObIcecastStreamer(obplayer.Config.setting('streamer_0_icecast_ip'), int(obplayer.Config.setting('streamer_0_icecast_port')), 43 | obplayer.Config.setting('streamer_0_icecast_username'), obplayer.Config.setting('streamer_0_icecast_password'), obplayer.Config.setting('streamer_0_icecast_mount'), 44 | obplayer.Config.setting('streamer_0_icecast_streamname'), obplayer.Config.setting('streamer_0_icecast_description'), 45 | obplayer.Config.setting('streamer_0_icecast_url'), obplayer.Config.setting('streamer_0_icecast_public'), obplayer.Config.setting('streamer_0_icecast_bitrate'), 46 | obplayer.Config.setting('streamer_0_title_streaming_enable')) 47 | # Start stream two 48 | obplayer.Streamer_stream_2 = ObIcecastStreamer(obplayer.Config.setting('streamer_1_icecast_ip'), int(obplayer.Config.setting('streamer_1_icecast_port')), 49 | obplayer.Config.setting('streamer_1_icecast_username'), obplayer.Config.setting('streamer_1_icecast_password'), obplayer.Config.setting('streamer_1_icecast_mount'), 50 | obplayer.Config.setting('streamer_1_icecast_streamname'), obplayer.Config.setting('streamer_1_icecast_description'), 51 | obplayer.Config.setting('streamer_1_icecast_url'), obplayer.Config.setting('streamer_1_icecast_public'), obplayer.Config.setting('streamer_1_icecast_bitrate'), 52 | obplayer.Config.setting('streamer_0_title_streaming_enable')) 53 | if obplayer.Config.setting('streamer_play_on_startup'): 54 | if obplayer.Config.setting('streamer_0_icecast_enable'): 55 | obplayer.Streamer_stream_1.start() 56 | # Title streaming is for mp3 only. 57 | if obplayer.Streamer_stream_1.mode == 'audio': 58 | obplayer.Streamer_stream_1.start_title_streaming() 59 | else: 60 | if obplayer.Streamer_stream_1.mode == 'audio': 61 | obplayer.Streamer_stream_1.stop_title_streaming() 62 | if obplayer.Config.setting('streamer_1_icecast_enable'): 63 | obplayer.Streamer_stream_2.start() 64 | obplayer.Streamer_stream_2.start_title_streaming() 65 | # Title streaming is for mp3 only. 66 | if obplayer.Streamer_stream_2.mode == 'audio': 67 | obplayer.Streamer_stream_2.start_title_streaming() 68 | else: 69 | if obplayer.Streamer_stream_2.mode == 'audio': 70 | obplayer.Streamer_stream_2.stop_title_streaming() 71 | 72 | GObject.timeout_add(1000, delaystart) 73 | 74 | obplayer.RTSPStreamer = None 75 | if obplayer.Config.setting('streamer_rtsp_enable'): 76 | from .rtsp import ObRTSPStreamer 77 | obplayer.RTSPStreamer = ObRTSPStreamer() 78 | 79 | if obplayer.Config.setting('streamer_rtp_enable'): 80 | from .rtp import ObRTPStreamer 81 | obplayer.ObRTPStreamer = ObRTPStreamer() 82 | obplayer.ObRTPStreamer.start() 83 | 84 | if obplayer.Config.setting('streamer_youtube_enable'): 85 | from .youtube import ObYoutubeStreamer 86 | obplayer.YoutubeStreamer = ObYoutubeStreamer() 87 | obplayer.YoutubeStreamer.start() 88 | 89 | def quit(): 90 | if obplayer.Streamer_stream_1: 91 | obplayer.Streamer_stream_1.stop_title_streaming() 92 | obplayer.Streamer_stream_1.quit() 93 | if obplayer.Streamer_stream_2: 94 | obplayer.Streamer_stream_2.stop_title_streaming() 95 | obplayer.Streamer_stream_2.quit() 96 | if obplayer.RTSPStreamer: 97 | obplayer.RTSPStreamer.quit() 98 | if obplayer.RTPStreamer: 99 | obplayer.RTPStreamer.quit() 100 | if obplayer.YoutubeStreamer: 101 | obplayer.YoutubeStreamer.quit() 102 | 103 | 104 | """ 105 | def start_streamer(name, clsname): 106 | exec("from .{0} import {1}".format(name, clsname)) 107 | streamer = eval(name)() 108 | streamer.start() 109 | return streamer 110 | """ 111 | -------------------------------------------------------------------------------- /obplayer/streamer/avahi.py: -------------------------------------------------------------------------------- 1 | # This file is part of avahi. 2 | # 3 | # avahi is free software; you can redistribute it and/or modify it 4 | # under the terms of the GNU Lesser General Public License as 5 | # published by the Free Software Foundation; either version 2 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # avahi is distributed in the hope that it will be useful, but WITHOUT 9 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 10 | # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 11 | # License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with avahi; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 16 | # USA. 17 | 18 | # Some definitions matching those in avahi-common/defs.h 19 | 20 | import dbus 21 | 22 | SERVER_INVALID, SERVER_REGISTERING, SERVER_RUNNING, SERVER_COLLISION, SERVER_FAILURE = range(0, 5) 23 | 24 | ENTRY_GROUP_UNCOMMITED, ENTRY_GROUP_REGISTERING, ENTRY_GROUP_ESTABLISHED, ENTRY_GROUP_COLLISION, ENTRY_GROUP_FAILURE = range(0, 5) 25 | 26 | DOMAIN_BROWSER_BROWSE, DOMAIN_BROWSER_BROWSE_DEFAULT, DOMAIN_BROWSER_REGISTER, DOMAIN_BROWSER_REGISTER_DEFAULT, DOMAIN_BROWSER_BROWSE_LEGACY = range(0, 5) 27 | 28 | PROTO_UNSPEC, PROTO_INET, PROTO_INET6 = -1, 0, 1 29 | 30 | IF_UNSPEC = -1 31 | 32 | PUBLISH_UNIQUE = 1 33 | PUBLISH_NO_PROBE = 2 34 | PUBLISH_NO_ANNOUNCE = 4 35 | PUBLISH_ALLOW_MULTIPLE = 8 36 | PUBLISH_NO_REVERSE = 16 37 | PUBLISH_NO_COOKIE = 32 38 | PUBLISH_UPDATE = 64 39 | PUBLISH_USE_WIDE_AREA = 128 40 | PUBLISH_USE_MULTICAST = 256 41 | 42 | LOOKUP_USE_WIDE_AREA = 1 43 | LOOKUP_USE_MULTICAST = 2 44 | LOOKUP_NO_TXT = 4 45 | LOOKUP_NO_ADDRESS = 8 46 | 47 | LOOKUP_RESULT_CACHED = 1 48 | LOOKUP_RESULT_WIDE_AREA = 2 49 | LOOKUP_RESULT_MULTICAST = 4 50 | LOOKUP_RESULT_LOCAL = 8 51 | LOOKUP_RESULT_OUR_OWN = 16 52 | LOOKUP_RESULT_STATIC = 32 53 | 54 | SERVICE_COOKIE = "org.freedesktop.Avahi.cookie" 55 | SERVICE_COOKIE_INVALID = 0 56 | 57 | DBUS_NAME = "org.freedesktop.Avahi" 58 | DBUS_INTERFACE_SERVER = DBUS_NAME + ".Server" 59 | DBUS_PATH_SERVER = "/" 60 | DBUS_INTERFACE_ENTRY_GROUP = DBUS_NAME + ".EntryGroup" 61 | DBUS_INTERFACE_DOMAIN_BROWSER = DBUS_NAME + ".DomainBrowser" 62 | DBUS_INTERFACE_SERVICE_TYPE_BROWSER = DBUS_NAME + ".ServiceTypeBrowser" 63 | DBUS_INTERFACE_SERVICE_BROWSER = DBUS_NAME + ".ServiceBrowser" 64 | DBUS_INTERFACE_ADDRESS_RESOLVER = DBUS_NAME + ".AddressResolver" 65 | DBUS_INTERFACE_HOST_NAME_RESOLVER = DBUS_NAME + ".HostNameResolver" 66 | DBUS_INTERFACE_SERVICE_RESOLVER = DBUS_NAME + ".ServiceResolver" 67 | DBUS_INTERFACE_RECORD_BROWSER = DBUS_NAME + ".RecordBrowser" 68 | 69 | def byte_array_to_string(s): 70 | r = "" 71 | 72 | for c in s: 73 | 74 | if c >= 32 and c < 127: 75 | r += "%c" % c 76 | else: 77 | r += "." 78 | 79 | return r 80 | 81 | def txt_array_to_string_array(t): 82 | l = [] 83 | 84 | for s in t: 85 | l.append(byte_array_to_string(s)) 86 | 87 | return l 88 | 89 | 90 | def string_to_byte_array(s): 91 | r = [] 92 | 93 | for c in s: 94 | r.append(dbus.Byte(ord(c))) 95 | 96 | return r 97 | 98 | def string_array_to_txt_array(t): 99 | l = [] 100 | 101 | for s in t: 102 | l.append(string_to_byte_array(s)) 103 | 104 | return l 105 | 106 | def dict_to_txt_array(txt_dict): 107 | l = [] 108 | 109 | for k,v in txt_dict.items(): 110 | l.append(string_to_byte_array("%s=%s" % (k,v))) 111 | 112 | return l 113 | -------------------------------------------------------------------------------- /obplayer/streamer/avahi_publish.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright 2012-2015 OpenBroadcaster, Inc. 6 | 7 | This file is part of OpenBroadcaster Player. 8 | 9 | OpenBroadcaster Player is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Affero General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | OpenBroadcaster Player is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Affero General Public License for more details. 18 | 19 | You should have received a copy of the GNU Affero General Public License 20 | along with OpenBroadcaster Player. If not, see . 21 | """ 22 | 23 | #import obplayer 24 | 25 | import gi 26 | from gi.repository import GObject 27 | 28 | import dbus 29 | import avahi 30 | from dbus.mainloop.glib import DBusGMainLoop 31 | 32 | service_name = "AudioOut@ObPlayer" 33 | service_type = "_rtsp._tcp" 34 | service_subtype = "_ravenna_session._sub._rtsp._tcp" 35 | service_port = 8554 36 | 37 | domain = "" # Domain to publish on, default to .local 38 | host = "" #obsource.local" # Host to publish records for, default to localhost 39 | 40 | group = None #our entry group 41 | rename_count = 12 # Counter so we only rename after collisions a sensible number of times 42 | 43 | def add_service(): 44 | global group, service_name, service_type, service_port, serviceTXT, domain, host 45 | if group is None: 46 | group = dbus.Interface( 47 | bus.get_object( avahi.DBUS_NAME, server.EntryGroupNew()), 48 | avahi.DBUS_INTERFACE_ENTRY_GROUP) 49 | group.connect_to_signal('StateChanged', entry_group_state_changed) 50 | 51 | #print("Adding service '%s' of type '%s' ..." % (service_name, service_type)) 52 | 53 | group.AddService( 54 | avahi.IF_UNSPEC, 55 | avahi.PROTO_UNSPEC, 56 | dbus.UInt32(0), 57 | service_name, service_type, 58 | domain, host, 59 | dbus.UInt16(service_port), 60 | []) 61 | group.AddServiceSubtype( 62 | avahi.IF_UNSPEC, 63 | avahi.PROTO_UNSPEC, 64 | dbus.UInt32(0), 65 | service_name, service_type, 66 | domain, 67 | service_subtype) 68 | group.Commit() 69 | 70 | def remove_service(): 71 | global group 72 | 73 | if not group is None: 74 | group.Reset() 75 | 76 | def server_state_changed(state): 77 | if state == avahi.SERVER_COLLISION: 78 | #print("WARNING: Server name collision") 79 | remove_service() 80 | elif state == avahi.SERVER_RUNNING: 81 | add_service() 82 | 83 | def entry_group_state_changed(state, error): 84 | global service_name, server, rename_count 85 | 86 | #print("state change: %i" % state) 87 | 88 | if state == avahi.ENTRY_GROUP_ESTABLISHED: 89 | #print("Service established.") 90 | pass 91 | elif state == avahi.ENTRY_GROUP_COLLISION: 92 | 93 | rename_count = rename_count - 1 94 | if rename_count > 0: 95 | name = server.GetAlternativeServiceName(name) 96 | print("WARNING: Service name collision, changing name to '%s' ..." % name) 97 | remove_service() 98 | add_service() 99 | 100 | else: 101 | print("ERROR: No suitable service name found after %i retries, exiting." % n_rename) 102 | main_loop.quit() 103 | elif state == avahi.ENTRY_GROUP_FAILURE: 104 | print("Error in group state changed", error) 105 | main_loop.quit() 106 | return 107 | 108 | 109 | 110 | 111 | if __name__ == '__main__': 112 | DBusGMainLoop( set_as_default=True ) 113 | 114 | main_loop = GObject.MainLoop() 115 | bus = dbus.SystemBus() 116 | 117 | server = dbus.Interface( 118 | bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ), 119 | avahi.DBUS_INTERFACE_SERVER ) 120 | 121 | server.connect_to_signal( "StateChanged", server_state_changed ) 122 | server_state_changed( server.GetState() ) 123 | 124 | 125 | try: 126 | main_loop.run() 127 | except KeyboardInterrupt: 128 | pass 129 | 130 | if not group is None: 131 | group.Free() 132 | 133 | 134 | -------------------------------------------------------------------------------- /obplayer/streamer/base.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright 2012-2015 OpenBroadcaster, Inc. 6 | 7 | This file is part of OpenBroadcaster Player. 8 | 9 | OpenBroadcaster Player is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Affero General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | OpenBroadcaster Player is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Affero General Public License for more details. 18 | 19 | You should have received a copy of the GNU Affero General Public License 20 | along with OpenBroadcaster Player. If not, see . 21 | """ 22 | 23 | import obplayer 24 | 25 | import os 26 | import time 27 | import traceback 28 | 29 | import gi 30 | gi.require_version('Gst', '1.0') 31 | from gi.repository import GObject, Gst 32 | 33 | 34 | class ObGstStreamer (object): 35 | def __init__(self, name): 36 | self.name = name 37 | self.pipeline = Gst.Pipeline() 38 | 39 | bus = self.pipeline.get_bus() 40 | bus.add_signal_watch() 41 | bus.connect("message", self.message_handler) 42 | 43 | def start(self): 44 | obplayer.Log.log("starting {0} streamer".format(self.name), 'debug') 45 | self.pipeline.set_state(Gst.State.PLAYING) 46 | 47 | def stop(self): 48 | obplayer.Log.log("stopping {0} streamer".format(self.name), 'debug') 49 | self.pipeline.set_state(Gst.State.NULL) 50 | 51 | def quit(self): 52 | self.pipeline.set_state(Gst.State.NULL) 53 | 54 | def restart_pipeline(self): 55 | self.wait_state(Gst.State.NULL) 56 | self.wait_state(Gst.State.PLAYING) 57 | 58 | def is_playing(self): 59 | (change, state, pending) = self.pipeline.get_state(0) 60 | if state == Gst.State.PLAYING: 61 | return True 62 | return False 63 | 64 | def build_pipeline(self, elements): 65 | for element in elements: 66 | obplayer.Log.log("adding element to bin: " + element.get_name(), 'debug') 67 | self.pipeline.add(element) 68 | for index in range(0, len(elements) - 1): 69 | elements[index].link(elements[index + 1]) 70 | 71 | def register_signals(self): 72 | bus = self.pipeline.get_bus() 73 | bus.add_signal_watch() 74 | bus.enable_sync_message_emission() 75 | bus.connect("sync-message::element", self.sync_handler) 76 | bus.connect("message", self.message_handler) 77 | 78 | def wait_state(self, target_state): 79 | self.pipeline.set_state(target_state) 80 | (statechange, state, pending) = self.pipeline.get_state(timeout=5 * Gst.SECOND) 81 | if statechange != Gst.StateChangeReturn.SUCCESS: 82 | obplayer.Log.log("gstreamer failed waiting for state change to " + str(pending), 'error') 83 | #raise Exception("Failed waiting for state change") 84 | return False 85 | return True 86 | 87 | # message handler (handles gstreamer messages posted to the bus) 88 | def message_handler(self, bus, message): 89 | if message.type == Gst.MessageType.ERROR: 90 | err, debug = message.parse_error() 91 | obplayer.Log.log("gstreamer error: %s, %s, %s" % (err, debug, err.code), 'error') 92 | obplayer.Log.log("attempting to restart {0} pipeline".format(self.name), 'info') 93 | GObject.timeout_add(5000, self.restart_pipeline) 94 | 95 | elif message.type == Gst.MessageType.WARNING: 96 | err, debug = message.parse_warning() 97 | obplayer.Log.log("gstreamer warning: %s, %s, %s" % (err, debug, err.code), 'warning') 98 | 99 | elif message.type == Gst.MessageType.INFO: 100 | err, debug = message.parse_info() 101 | obplayer.Log.log("gstreamer info: %s, %s, %s" % (err, debug, err.code), 'info') 102 | 103 | 104 | -------------------------------------------------------------------------------- /obplayer/streamer/metadata_updater.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from requests.auth import HTTPBasicAuth 3 | import time 4 | import obplayer 5 | import urllib.parse 6 | # NOTE: The threading module is being used due a issue with the obplayer threading system 7 | # not being able to handle a threat thats not running. 8 | import threading 9 | 10 | class MetadataUpdater(threading.Thread): 11 | def __init__(self, protocol='http', host=None, port='8000', username='source', password='', mount=None): 12 | threading.Thread.__init__(self) 13 | self._protocol = protocol 14 | self._host = host 15 | self._port = port 16 | self._username = username 17 | self._password = password 18 | self._mount = mount 19 | self._last_track = None 20 | self.running = True 21 | 22 | # Sends the current track info to icecast. 23 | 24 | def _post_metadata_update(self, current_track): 25 | url = 'http://{0}:{1}/admin/metadata?mode=updinfo&mount=/{2}&song={3}'.format(self._host, self._port, self._mount, urllib.parse.quote(current_track)) 26 | req = requests.get(url, auth=(self._username, self._password)) 27 | if req.status_code == 200: 28 | return True 29 | else: 30 | return False 31 | 32 | # Gets the current track playing now, and returns it. 33 | def _get_currently_playing(self): 34 | try: 35 | requests = obplayer.Player.get_requests() 36 | select_keys = ['artist', 'title'] 37 | artist = requests['audio']['artist'] 38 | title = requests['audio']['title'] 39 | return artist + ' - ' + title 40 | except Exception as e: 41 | # handle catching a time while nothing is playing. 42 | return self._last_track 43 | 44 | def run(self): 45 | # sleep for 4 seconds to make sure the stream was on the 46 | # server before handling the first update request. 47 | time.sleep(4) 48 | while self.running: 49 | new_track = self._get_currently_playing() 50 | if new_track != None: 51 | if new_track != self._last_track: 52 | self._last_track = new_track 53 | if self._post_metadata_update(self._last_track) == False: 54 | obplayer.Log.log('The request to update the now playing track fai\'ld! This mostly likly means your password for your stream is wrong, or that your server is having issues.', 'error') 55 | else: 56 | obplayer.Log.log('"{0}" has been sent to icecast via title streaming.'.format(self._last_track), 'debug') 57 | time.sleep(4) 58 | 59 | def stop(self): 60 | obplayer.ObThread.stop(self) 61 | self.running = False 62 | -------------------------------------------------------------------------------- /obplayer/streamer/rtsp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright 2012-2015 OpenBroadcaster, Inc. 6 | 7 | This file is part of OpenBroadcaster Player. 8 | 9 | OpenBroadcaster Player is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Affero General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | OpenBroadcaster Player is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Affero General Public License for more details. 18 | 19 | You should have received a copy of the GNU Affero General Public License 20 | along with OpenBroadcaster Player. If not, see . 21 | """ 22 | 23 | import obplayer 24 | 25 | import os 26 | import time 27 | import traceback 28 | 29 | import gi 30 | gi.require_version('Gst', '1.0') 31 | gi.require_version('GstNet', '1.0') 32 | gi.require_version('GstRtsp', '1.0') 33 | gi.require_version('GstRtspServer', '1.0') 34 | from gi.repository import GObject, Gst, GstNet, GstRtsp, GstRtspServer 35 | 36 | 37 | 38 | class ObRTSPStreamer (object): 39 | def __init__(self): 40 | self.discovery_proc = None 41 | self.server = GstRtspServer.RTSPServer() 42 | self.server.set_service(str(obplayer.Config.setting('streamer_rtsp_port'))) 43 | self.server.connect("client-connected", self.client_connected) 44 | 45 | self.clock_rate = obplayer.Config.setting('streamer_rtsp_clock_rate') 46 | 47 | #auth = GstRtspServer.RTSPAuth.new() 48 | #self.server.set_auth(auth) 49 | 50 | factory = GstRtspServer.RTSPMediaFactory.new() 51 | #factory.set_launch('( uridecodebin uri="file:///media/obsuser/Wheatley/GoaTrance.mp3" is-live=1 ! audioconvert ! lamemp3enc ! rtpmpapay name=pay0 pt=96 )') 52 | #factory.set_launch('( uridecodebin uri="file:///media/obsuser/Wheatley/GoaTrance.mp3" is-live=1 ! audioconvert ! audioresample ! "audio/x-raw,clock-rate=48000,channels=2" ! rtpL24pay name=pay0 pt=96 )') 53 | #factory.set_launch('uridecodebin uri="file:///media/obsuser/Wheatley/GoaTrance.mp3" is-live=1 ! audioresample ! audioconvert ! capsfilter caps="audio/x-raw,rate=48000,channels=2" ! queue2 ! rtpL24pay name=pay0 pt=96 max-ptime=1000000') 54 | factory.set_launch('pulsesrc client-name="AudioOut@ObPlayer" ! audioresample ! audioconvert ! capsfilter caps="audio/x-raw,rate=' + self.clock_rate + ',channels=2" ! queue2 ! rtpL24pay name=pay0 pt=96 max-ptime=1000000') 55 | #factory.set_launch("( videotestsrc is-live=1 ! x264enc ! rtph264pay name=pay0 pt=96 )") 56 | factory.set_shared(True) 57 | #factory.set_protocols(GstRtsp.RTSPLowerTrans.UDP | GstRtsp.RTSPLowerTrans.UDP_MCAST) 58 | factory.set_protocols(GstRtsp.RTSPLowerTrans.UDP_MCAST) 59 | factory.set_transport_mode(GstRtspServer.RTSPTransportMode.PLAY) 60 | #factory.set_clock(GstNet.PtpClock.new()) 61 | factory.set_latency(1) 62 | factory.connect("media-configure", self.media_configure) 63 | 64 | addrpool = GstRtspServer.RTSPAddressPool.new() 65 | addrpool.add_range('239.192.1.101', '239.192.1.108', 5004, 5008, 100) 66 | factory.set_address_pool(addrpool) 67 | 68 | mounts = self.server.get_mount_points() 69 | mounts.add_factory('/by-id/1', factory) 70 | mounts.add_factory('/by-name/AudioOut%40ObPlayer', factory) 71 | 72 | # NOTE this is to circumvent a bug in Axia xNode (2.0.0r): non-numeric session IDs are ignored 73 | class SessPool (GstRtspServer.RTSPSessionPool): 74 | last = 1 75 | def do_create_session_id(self): 76 | self.last += 1 77 | return str(self.last) 78 | sesspool = SessPool() 79 | #sesspool = GstRtspServer.RTSPSessionPool.new() 80 | self.server.set_session_pool(sesspool) 81 | 82 | self.server.attach(None) 83 | 84 | if obplayer.Config.setting('streamer_rtsp_allow_discovery'): 85 | self.start_discovery() 86 | 87 | def client_connected(self, server, client): 88 | obplayer.Log.log('client connected to RTSP streaming server', 'debug') 89 | 90 | def media_configure(self, factory, media): 91 | obplayer.Log.log('RTSP streaming server media configured', 'debug') 92 | 93 | def quit(self): 94 | if self.discovery_proc: 95 | self.discovery_proc.terminate() 96 | 97 | def start_discovery(self): 98 | import subprocess 99 | 100 | obplayer.Log.log('starting avahi service discovery for RTSP/Ravenna', 'debug') 101 | self.discovery_proc = subprocess.Popen([ 'obplayer/streamer/avahi_publish.py' ], close_fds=True) 102 | 103 | -------------------------------------------------------------------------------- /obplayer/task.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright 2012-2015 OpenBroadcaster, Inc. 6 | 7 | This file is part of OpenBroadcaster Player. 8 | 9 | OpenBroadcaster Player is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Affero General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | OpenBroadcaster Player is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Affero General Public License for more details. 18 | 19 | You should have received a copy of the GNU Affero General Public License 20 | along with OpenBroadcaster Player. If not, see . 21 | """ 22 | 23 | import obplayer 24 | 25 | import threading 26 | 27 | import traceback 28 | 29 | class ObThread (threading.Thread): 30 | threads = [] 31 | 32 | def __init__(self, name=None, target=None): 33 | if name is None: 34 | name = self.__class__.__name__ 35 | threading.Thread.__init__(self, None, None, name) 36 | ObThread.threads.insert(0, self) 37 | self.stopflag = threading.Event() 38 | self.target = target 39 | 40 | def remove_thread(self): 41 | ObThread.threads.remove(self) 42 | 43 | def start(self): 44 | obplayer.Log.log("starting thread <%s>" % (str(self.name),), 'debug') 45 | threading.Thread.start(self) 46 | 47 | def stop(self): 48 | obplayer.Log.log("stopping thread <%s>" % (str(self.name),), 'debug') 49 | self.stopflag.set() 50 | 51 | @staticmethod 52 | def stop_all(): 53 | for t in ObThread.threads: 54 | t.stop() 55 | 56 | @staticmethod 57 | def join_all(): 58 | for t in ObThread.threads: 59 | if t.daemon is False: 60 | t.join() 61 | obplayer.Log.log("thread <%s> has joined successfully" % (str(t.name),), 'debug') 62 | else: 63 | obplayer.Log.log("thread <%s> is daemon, skipping" % (str(t.name),), 'debug') 64 | 65 | def run(self): 66 | try: 67 | if self.target: 68 | self.target() 69 | else: 70 | self.try_run() 71 | except: 72 | obplayer.Log.log("exception occurred in thread " + str(self.name) + ":", 'error') 73 | obplayer.Log.log(traceback.format_exc(), 'error') 74 | finally: 75 | del self.target 76 | 77 | -------------------------------------------------------------------------------- /obplayer/testsignal/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright 2012-2015 OpenBroadcaster, Inc. 6 | 7 | This file is part of OpenBroadcaster Player. 8 | 9 | OpenBroadcaster Player is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Affero General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | OpenBroadcaster Player is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Affero General Public License for more details. 18 | 19 | You should have received a copy of the GNU Affero General Public License 20 | along with OpenBroadcaster Player. If not, see . 21 | """ 22 | 23 | from __future__ import absolute_import 24 | 25 | import obplayer 26 | 27 | def init(): 28 | def testsignal_request(self, present_time, media_class): 29 | self.add_request(media_type='break', duration=5) 30 | self.add_request(media_type='testsignal', duration=31536000) # duration = 1 year (ie. indefinitely) 31 | 32 | ctrl = obplayer.Player.create_controller('testsignal', priority=2, allow_requeue=False) 33 | ctrl.set_request_callback(testsignal_request) 34 | 35 | def quit(): 36 | pass 37 | 38 | -------------------------------------------------------------------------------- /obplayer/ui.glade: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 100 6 | 1 7 | 10 8 | 9 | 10 | 11 | False 12 | audio-volume-high 13 | 14 | 15 | 16 | True 17 | False 18 | vertical 19 | 20 | 21 | True 22 | False 23 | both 24 | 25 | 26 | True 27 | False 28 | Fullscreen 29 | True 30 | gtk-fullscreen 31 | 32 | 33 | 34 | False 35 | True 36 | 37 | 38 | 39 | 40 | True 41 | False 42 | Quit 43 | True 44 | gtk-quit 45 | 46 | 47 | 48 | False 49 | True 50 | 51 | 52 | 53 | 54 | False 55 | True 56 | 0 57 | 58 | 59 | 60 | 61 | True 62 | True 63 | vertical 64 | 65 | 66 | True 67 | False 68 | 69 | 70 | 71 | False 72 | True 73 | 74 | 75 | 76 | 77 | True 78 | True 79 | 1 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /obplayer/xrandr/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright 2012-2015 OpenBroadcaster, Inc. 6 | 7 | This file is part of OpenBroadcaster Player. 8 | 9 | OpenBroadcaster Player is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Affero General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | OpenBroadcaster Player is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Affero General Public License for more details. 18 | 19 | You should have received a copy of the GNU Affero General Public License 20 | along with OpenBroadcaster Player. If not, see . 21 | """ 22 | 23 | from __future__ import absolute_import 24 | 25 | import obplayer 26 | 27 | import os 28 | import sys 29 | import time 30 | import os.path 31 | import traceback 32 | 33 | import subprocess 34 | 35 | 36 | output = None 37 | modes = [ ] 38 | xrandrcmd = '/usr/bin/xrandr' 39 | 40 | def init(): 41 | load_modes() 42 | mode = obplayer.Config.setting('video_out_resolution') 43 | #if not obplayer.Config.headless: 44 | # set_mode(mode) 45 | 46 | def quit(): 47 | # TODO can you save the resolution and restore it here? 48 | pass 49 | 50 | def load_modes(): 51 | global modes, output 52 | modes = [ ] 53 | 54 | if not os.path.exists(xrandrcmd): 55 | return 56 | 57 | proc = subprocess.Popen([ xrandrcmd ], stdout=subprocess.PIPE) 58 | (output, _) = proc.communicate() 59 | 60 | displays = [ ] 61 | for line in output.split(b'\n'): 62 | if line.startswith(b' '): 63 | if len(displays) <= 0: 64 | obplayer.Log.log("error reading output of xrandr", 'error') 65 | break 66 | displays[-1].append(line.strip().split()[0]) 67 | elif b' connected' in line: 68 | displays.append([ line ]) 69 | 70 | #print(displays) 71 | 72 | use = None 73 | for display in displays: 74 | if b'connected primary' in display[0]: 75 | use = display 76 | break 77 | if not use: 78 | use = displays[0] 79 | 80 | output = use[0].decode('utf-8').split(' ')[0] 81 | for mode in use[1:]: 82 | modes.append(mode.decode('utf-8')) 83 | 84 | #print(output) 85 | #print(modes) 86 | 87 | def get_modes(): 88 | return modes 89 | 90 | def set_mode(mode): 91 | if mode == 'default': 92 | return 93 | 94 | if mode not in modes: 95 | obplayer.Log.log("invalid xrandr video mode " + mode, 'error') 96 | return 97 | 98 | if not os.path.exists(xrandrcmd): 99 | obplayer.Log.log("xrandr binary is missing; unable to change resolution", 'error') 100 | return 101 | 102 | obplayer.Log.log("changing xrandr video mode to " + mode, 'player') 103 | os.system('{0} --output {1} --mode {2}'.format(xrandrcmd, output, mode)) 104 | 105 | -------------------------------------------------------------------------------- /obplayer_check: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | running_check=`ps -aef | grep "python? [o]bplayer.py" | wc -l` 4 | 5 | if [ "$running_check" -ge "1" ] 6 | then 7 | echo OpenBroadcaster Player already running. 8 | exit; 9 | fi 10 | 11 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 12 | cd $DIR 13 | 14 | if [ ! -x "/usr/bin/python3" ] || ! python3 -c "import apsw" 15 | then 16 | #echo "Switching to legacy OpenBroadcaster Player" 17 | #git checkout legacy 18 | #if [ $? -ne 0 ]; then 19 | # echo "" 20 | # echo "ERROR: failed to switch to the legacy branch. Python2 is no longer supported. Please install the Python3 dependencies listed in dependencies.txt of the source file and retry, or check out the \"legacy\" branch from the git repository to continue using Python2" 21 | # echo "" 22 | 23 | python2 obplayer.py $@ 24 | #fi 25 | else 26 | python3 obplayer.py $@ 27 | fi 28 | -------------------------------------------------------------------------------- /obplayer_loop: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | RESET="" 4 | 5 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 6 | cd $DIR 7 | 8 | while [ 1 ] 9 | do 10 | ./obplayer_check $@ $RESET 11 | 12 | if [ $? -eq 37 ] 13 | then 14 | echo "deleting data.db and restarting with -r" 15 | #rm ~/.openbroadcaster/data.db 16 | RESET="-r" 17 | else 18 | RESET="" 19 | fi 20 | sleep 4 21 | done 22 | -------------------------------------------------------------------------------- /tools/audiolog_concat_example1.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # concat yesterday's logs to a single ogg file 4 | 5 | date=`date -d "yesterday 13:00" +%Y%m%d` 6 | rm -f ~/.audiolog_concat.list 7 | for file in ~/.openbroadcaster/lineinlogs/$date-*; 8 | do echo file \'$file\' >> ~/.audiolog_concat.list; 9 | done 10 | sort -o ~/.audiolog_concat.list ~/.audiolog_concat.list 11 | ffmpeg -f concat -safe 0 -i ~/.audiolog_concat.list -c copy ~/.openbroadcaster/lineinlogs/$date.ogg 12 | -------------------------------------------------------------------------------- /tools/audiolog_concat_example2.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # concat today's logs to mp3; make sure there are no current recordings first (lock files). 4 | 5 | date=`date +%Y%m%d` 6 | 7 | locked=`ls -l ~/.openbroadcaster/lineinlogs/$date-*.lock | wc -l` 8 | while [[ $locked != "0" ]] 9 | do 10 | sleep 60 11 | locked=`ls -l ~/.openbroadcaster/lineinlogs/$date-*.lock | wc -l` 12 | done 13 | 14 | rm -f ~/.audiolog_concat.list 15 | for file in ~/.openbroadcaster/lineinlogs/$date-*; 16 | do echo file \'$file\' >> ~/.audiolog_concat.list; 17 | done 18 | 19 | sort -o ~/.audiolog_concat.list ~/.audiolog_concat.list 20 | 21 | ffmpeg -f concat -safe 0 -i ~/.audiolog_concat.list ~/.openbroadcaster/lineinlogs/$date.mp3 22 | -------------------------------------------------------------------------------- /tools/audiolog_upload: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import argparse 4 | import pycurl 5 | import os 6 | from urllib.parse import urlencode 7 | import json 8 | 9 | parser = argparse.ArgumentParser(prog='audiolog_upload', formatter_class=argparse.ArgumentDefaultsHelpFormatter, description="Upload audio logs to OpenBroadcaster Server.") 10 | parser.add_argument('-u', '--username', type=str, help='Username', default='obplayer') 11 | parser.add_argument('-p', '--password', type=str, help='Password', default='obplayer') 12 | parser.add_argument('-s', '--source', type=str, help='Log file drectory', default='~/.openbroadcaster/lineinlogs') 13 | parser.add_argument('-d', '--destination', type=str, help='OpenBroadcaster Server URL', default='http://127.0.0.1/') 14 | parser.add_argument('-c', '--category', type=int, help='Category ID', default=1) 15 | parser.add_argument('-g', '--genre', type=int, help='Genre ID', default=1); 16 | 17 | args = parser.parse_args() 18 | args.source = os.path.expanduser(args.source) 19 | 20 | # 21 | # FRIENDLY CURL RESPONSE HELPER 22 | # 23 | 24 | class CurlResponse: 25 | def __init__(self): 26 | self.buffer = u'' 27 | def __call__(self, data): 28 | self.buffer += data.decode('utf-8') 29 | 30 | # 31 | # LOGIN TO OPENBROADCASTER SERVER 32 | # 33 | 34 | curl = pycurl.Curl() 35 | curl_response = CurlResponse() 36 | 37 | postfields = {} 38 | postfields['c'] = 'account' 39 | postfields['a'] = 'login' 40 | postfields['d'] = json.dumps({'username': args.username, 'password': args.password}) 41 | 42 | enc_postfields = urlencode(postfields) 43 | 44 | curl.setopt(pycurl.URL, args.destination+'/api.php') 45 | curl.setopt(pycurl.HEADER, False) 46 | curl.setopt(pycurl.POST, True) 47 | curl.setopt(pycurl.POSTFIELDS, enc_postfields) 48 | curl.setopt(pycurl.WRITEFUNCTION, curl_response) 49 | 50 | curl.perform() 51 | curl.close() 52 | 53 | if curl_response.buffer == '': 54 | print('API access failed.') 55 | exit() 56 | 57 | try: 58 | login_response = json.loads(curl_response.buffer) 59 | except ValueError: 60 | print('API access failed.') 61 | exit() 62 | 63 | if login_response['status'] != True: 64 | print('Login failed.') 65 | exit() 66 | 67 | # 68 | # LOOP THROUGH FILES 69 | # 70 | 71 | for filename in os.listdir(args.source): 72 | 73 | if filename[-5:]=='.lock': 74 | print('Skipping locked file: '+filename) 75 | continue 76 | 77 | if filename[-9:]=='.uploaded': 78 | print('Skipping uploaded file: '+filename) 79 | continue 80 | 81 | filepath = os.path.expanduser(args.source+'/'+filename) 82 | 83 | # 84 | # UPLOAD FILE 85 | # 86 | 87 | curl = pycurl.Curl() 88 | curl_response = CurlResponse() 89 | 90 | f = open(filepath, 'rb') 91 | fs = os.path.getsize(filepath) 92 | 93 | postfields = {} 94 | postfields['i'] = login_response['data']['id'] 95 | postfields['k'] = login_response['data']['key'] 96 | 97 | enc_postfields = urlencode(postfields) 98 | 99 | curl.setopt(pycurl.URL, args.destination+'/upload.php') 100 | curl.setopt(pycurl.PUT, True) 101 | curl.setopt(pycurl.INFILE,f) 102 | curl.setopt(pycurl.INFILESIZE,fs) 103 | curl.setopt(pycurl.HTTPHEADER, ['Expect:']) 104 | curl.setopt(pycurl.WRITEFUNCTION, curl_response) 105 | 106 | curl.perform() 107 | curl.close() 108 | 109 | if curl_response.buffer == '': 110 | print('API access failed: '+filename) 111 | continue 112 | 113 | try: 114 | upload_response = json.loads(curl_response.buffer) 115 | except ValueError: 116 | print('Upload failed: '+filename) 117 | continue 118 | 119 | if upload_response['media_supported'] != True: 120 | print('Media not supported by server: '+filename) 121 | continue 122 | 123 | # 124 | # ADD MEDIA ITEM FROM UPLOADED FILE 125 | # 126 | 127 | media = {} 128 | media['file_id'] = upload_response['file_id'] 129 | media['file_key'] = upload_response['file_key'] 130 | media['artist'] = 'Audio Log' 131 | media['title'] = os.path.splitext(filename)[0] 132 | media['category_id'] = args.category 133 | media['genre_id'] = args.genre 134 | media['status'] = 'public' 135 | media['local_id'] = 1 136 | 137 | curl = pycurl.Curl() 138 | curl_response = CurlResponse() 139 | 140 | postfields = {} 141 | postfields['i'] = login_response['data']['id'] 142 | postfields['k'] = login_response['data']['key'] 143 | postfields['c'] = 'media' 144 | postfields['a'] = 'edit' 145 | postfields['d'] = json.dumps({'media': [media]}) 146 | 147 | enc_postfields = urlencode(postfields) 148 | 149 | curl.setopt(pycurl.URL, args.destination+'/api.php') 150 | curl.setopt(pycurl.HEADER, False) 151 | curl.setopt(pycurl.POST, True) 152 | curl.setopt(pycurl.POSTFIELDS, enc_postfields) 153 | curl.setopt(pycurl.WRITEFUNCTION, curl_response) 154 | 155 | curl.perform() 156 | curl.close() 157 | 158 | if curl_response.buffer == '': 159 | print('Media save failed: '+filename) 160 | continue 161 | 162 | try: 163 | save_response = json.loads(curl_response.buffer) 164 | except ValueError: 165 | print('Media save failed: '+filename) 166 | continue 167 | 168 | if save_response['status'] != True: 169 | print('Media save failed: '+filename) 170 | continue 171 | 172 | os.rename(filepath, filepath+'.uploaded') 173 | print('Success: '+filename) -------------------------------------------------------------------------------- /tools/live_monitor: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | # OpenBroadcaster off-the-air Recorder 5 | 6 | import os 7 | import sys 8 | import time 9 | import argparse 10 | 11 | import gi 12 | gi.require_version('Gst', '1.0') 13 | from gi.repository import GObject, Gst 14 | 15 | GObject.threads_init() 16 | Gst.init(None) 17 | 18 | 19 | class ObRecorder: 20 | def __init__(self): 21 | parser = argparse.ArgumentParser(prog='obplayer_monitor', formatter_class=argparse.ArgumentDefaultsHelpFormatter, description='Record off-the-air audio, encode to ogg, and save to destination directory (one file per hour).') 22 | parser.add_argument('-D', '--alsa-device', type=str, help='Name of the ALSA input device.', default='default') 23 | parser.add_argument('-R', '--sample-rate', type=str, help='Output sample rate in hz.', default='22050') 24 | parser.add_argument('-C', '--channels', type=str, help='Number of channels. 1 for mono, 2 for stereo.', default='1') 25 | parser.add_argument('dest', help='File destination directory.', type=str) 26 | 27 | self.args = parser.parse_args() 28 | 29 | if os.access(self.args.dest, os.W_OK) == False: 30 | print 'Destination directory does not exist, or write permission denied.' 31 | exit(1) 32 | 33 | def start(self): 34 | # determine filename... 35 | outfile = self.args.dest + '/' + time.strftime('%Y-%m-%d_%H:%M:%S') + '.flac' 36 | 37 | launchcmd = 'alsasrc device=' + self.args.alsa_device \ 38 | + ' ! audioconvert ! audioresample ' \ 39 | + ' ! audio/x-raw, rate=' + self.args.sample_rate + ', channels=' + self.args.channels \ 40 | + ' ! queue ! flacenc quality=5 ! filesink location=' + outfile 41 | 42 | self.pipeline = Gst.parse_launch(launchcmd) 43 | self.pipeline.set_state(Gst.State.PLAYING) 44 | 45 | def stop(self): 46 | self.pipeline.set_state(Gst.State.NULL) 47 | del self.pipeline 48 | 49 | 50 | def main(): 51 | mainloop = GObject.MainLoop() 52 | 53 | record = ObRecorder() 54 | record.start() 55 | 56 | try: 57 | mainloop.run() 58 | except KeyboardInterrupt: 59 | pass 60 | 61 | record.stop() 62 | sys.exit(0) 63 | 64 | main() 65 | 66 | -------------------------------------------------------------------------------- /tools/livewire-example.sdp: -------------------------------------------------------------------------------- 1 | v=0 2 | o=- 2 1 IN IP4 172.16.0.15 3 | s=Channel 102 4 | t=0 0 5 | a=clock-domain:PTPv2 0 6 | a=type:multicast 7 | m=audio 5004 RTP/AVP 96 8 | c=IN IP4 239.192.0.102/0 9 | a=rtpmap:96 L24/48000/2 10 | a=sync-time:0 11 | -------------------------------------------------------------------------------- /tools/local_streamer.sdp: -------------------------------------------------------------------------------- 1 | v=0 2 | s=Local Streamer 3 | t=0 0 4 | a=recvonly 5 | a=sync-time:0 6 | a=type:unicast 7 | c=IN IP4 0.0.0.0 8 | m=audio 5004 RTP/AVP 96 9 | a=rtpmap:96 OPUS/48000/2 10 | -------------------------------------------------------------------------------- /tools/obplayer_monitor: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | # OpenBroadcaster off-the-air Recorder 5 | 6 | import os 7 | import sys 8 | import time 9 | import argparse 10 | 11 | import gi 12 | gi.require_version('Gst', '1.0') 13 | from gi.repository import GObject, Gst 14 | 15 | GObject.threads_init() 16 | Gst.init(None) 17 | 18 | 19 | class ObRecorder: 20 | 21 | def __init__(self): 22 | parser = argparse.ArgumentParser(prog='obremote_monitor', formatter_class=argparse.ArgumentDefaultsHelpFormatter, description='Record off-the-air audio, encode to ogg, and save to destination directory (one file per hour).') 23 | parser.add_argument('-D', '--alsa-device', type=str, help='Name of the ALSA input device.', default='default') 24 | parser.add_argument('-R', '--sample-rate', type=str, help='Output sample rate in hz.', default='22050') 25 | parser.add_argument('-C', '--channels', type=str, help='Number of channels. 1 for mono, 2 for stereo.', default='1') 26 | parser.add_argument('dest', help='File destination directory.', type=str) 27 | 28 | self.args = parser.parse_args() 29 | 30 | if os.access(self.args.dest, os.W_OK) == False: 31 | print 'Destination directory does not exist, or write permission denied.' 32 | exit(1) 33 | 34 | self.date = time.strftime('%Y-%m-%d-%H') 35 | 36 | def start(self): 37 | outfile = self.args.dest + '/' + time.strftime('%Y-%m-%d_%H:%M:%S') + '.ogg' 38 | 39 | launchcmd = 'alsasrc device=' + self.args.alsa_device \ 40 | + ' ! audioconvert ! audioresample ' \ 41 | + ' ! audio/x-raw, rate=' + self.args.sample_rate + ', channels=' + self.args.channels \ 42 | + ' ! queue ! vorbisenc quality=0.0 ! oggmux ! queue ! filesink location=' + outfile 43 | 44 | self.pipeline = Gst.parse_launch(launchcmd) 45 | self.pipeline.set_state(Gst.State.PLAYING) 46 | self.log_rotate() 47 | 48 | def stop(self): 49 | self.pipeline.set_state(Gst.State.NULL) 50 | del self.pipeline 51 | 52 | def log_rotate(self): 53 | if self.date != time.strftime('%Y-%m-%d-%H'): 54 | self.date = time.strftime('%Y-%m-%d-%H') 55 | self.stop() 56 | self.start() 57 | GObject.timeout_add(1.0, self.log_rotate) 58 | 59 | 60 | def main(): 61 | mainloop = GObject.MainLoop() 62 | 63 | record = ObRecorder() 64 | record.start() 65 | 66 | try: 67 | mainloop.run() 68 | except KeyboardInterrupt: 69 | pass 70 | 71 | record.stop() 72 | sys.exit(0) 73 | 74 | main() 75 | 76 | -------------------------------------------------------------------------------- /tools/obremote_monitor_arecord: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | while true 4 | do 5 | /usr/bin/arecord -Dplug:cjuc_monitor -f S32_LE -t raw | /usr/bin/oggenc - -r -R 8000 -C 2 --downmix -o /media/obdrive/cjuc_monitor/`date +%Y-%m-%d_%H:%M:%S`.ogg 6 | sleep 1 7 | done 8 | -------------------------------------------------------------------------------- /tools/priority_stream: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import sys 6 | import time 7 | import argparse 8 | 9 | import gi 10 | gi.require_version('Gst', '1.0') 11 | from gi.repository import GObject, Gst 12 | 13 | GObject.threads_init() 14 | Gst.init(None) 15 | 16 | 17 | class ObPriorityStream (object): 18 | def __init__(self): 19 | parser = argparse.ArgumentParser(prog='priority_stream', formatter_class=argparse.ArgumentDefaultsHelpFormatter, description="Stream audio to an icecast server only when audio is playing.") 20 | parser.add_argument('-D', '--alsa-device', type=str, help='Name of the ALSA device to monitor.', default='default') 21 | parser.add_argument('-i', '--ip', type=str, help='Icecast server IP address.', default='127.0.0.1') 22 | parser.add_argument('-P', '--port', type=str, help='Icecast server port number.', default='8000') 23 | parser.add_argument('-p', '--password', type=str, help='Icecast server password.', default='') 24 | parser.add_argument('-m', '--mount', type=str, help='Icecast mountpoint to send to.', default='stream') 25 | 26 | self.args = parser.parse_args() 27 | 28 | self.create_pipeline() 29 | 30 | def create_pipeline(self): 31 | self.pipeline = Gst.Pipeline() 32 | 33 | self.input_pipe = [ ] 34 | 35 | self.audio_input = Gst.ElementFactory.make("alsasrc", "alsasrc") 36 | self.audio_input.set_property('device', self.args.alsa_device) 37 | self.input_pipe.append(self.audio_input) 38 | 39 | self.level = Gst.ElementFactory.make("level", "level") 40 | self.level.set_property('message', True) 41 | self.level.set_property('interval', int(1.0 * Gst.SECOND)) 42 | self.input_pipe.append(self.level) 43 | 44 | self.selector = Gst.ElementFactory.make("valve", "selector") 45 | self.input_pipe.append(self.selector) 46 | 47 | self.build_pipeline(self.input_pipe) 48 | 49 | 50 | self.output_pipe = [ ] 51 | self.encoder = Gst.ElementFactory.make("lamemp3enc", "lamemp3enc") 52 | self.output_pipe.append(self.encoder) 53 | 54 | self.shout2send = Gst.ElementFactory.make("shout2send", "shout2send") 55 | self.shout2send.set_property('ip', self.args.ip) 56 | self.shout2send.set_property('port', int(self.args.port)) 57 | self.shout2send.set_property('password', self.args.password) 58 | self.shout2send.set_property('mount', self.args.mount) 59 | self.output_pipe.append(self.shout2send) 60 | 61 | self.build_pipeline(self.output_pipe) 62 | 63 | self.selector.link(self.encoder) 64 | 65 | 66 | """ 67 | self.fake_pipe = [ ] 68 | self.fakesink = Gst.ElementFactory.make("fakesink", "fakesink") 69 | self.fake_pipe.append(self.fakesink) 70 | 71 | self.build_pipeline(self.fake_pipe) 72 | 73 | self.selector.link(self.fakesink) 74 | """ 75 | 76 | 77 | #self.selector.set_property('active-pad', self.selector.get_request_pad('sink0')) 78 | 79 | self.is_dropping = True 80 | self.selector.set_property('drop', True) 81 | 82 | self.pipeline.get_bus().add_signal_watch() 83 | self.pipeline.get_bus().connect('message::element', self.detect_silence) 84 | 85 | def build_pipeline(self, elements): 86 | for element in elements: 87 | #print "adding element to bin: " + element.get_name() 88 | self.pipeline.add(element) 89 | for index in range(0, len(elements) - 1): 90 | elements[index].link(elements[index + 1]) 91 | 92 | def start(self): 93 | self.pipeline.set_state(Gst.State.PLAYING) 94 | 95 | def stop(self): 96 | self.pipeline.set_state(Gst.State.NULL) 97 | 98 | def detect_silence(self, bus, message, *args): 99 | peak = message.get_structure().get_value('peak') 100 | if peak[0] < -28: 101 | if not self.is_dropping: 102 | self.is_dropping = True 103 | self.selector.set_property('drop', True) 104 | print "now dropping buffers" 105 | else: 106 | if self.is_dropping: 107 | self.is_dropping = False 108 | self.selector.set_property('drop', False) 109 | print "now outputting buffers" 110 | return True 111 | 112 | 113 | def main(): 114 | mainloop = GObject.MainLoop() 115 | 116 | stream = ObPriorityStream() 117 | stream.start() 118 | 119 | try: 120 | mainloop.run() 121 | except KeyboardInterrupt: 122 | pass 123 | 124 | stream.stop() 125 | sys.exit(0) 126 | 127 | main() 128 | 129 | -------------------------------------------------------------------------------- /updater: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | # log message 6 | function log() { 7 | message=$1 8 | #print to the shell incase someone ran this manually.' 9 | DATE=$(date) 10 | echo "$DATE: $message" 11 | echo "$DATE: $message" > $LOG_FILE 12 | } 13 | 14 | function run_upgrade() { 15 | log "*** Upgrading OS packages... ***" 16 | apt upgrade -y 17 | log "*** OS updating complete. System should reboot now. ***" 18 | #reboot 19 | } 20 | 21 | UPDATE_FILE="/tmp/obplayer.update" 22 | LOG_FILE=$1 23 | 24 | if [[ -f "$UPDATE_FILE" ]]; then 25 | log "*** Checking for OS updates... ***" 26 | DATA=$(apt update) 27 | # echo $DATA 28 | # check for updates. 29 | if [[ $DATA =~ "packages can be upgraded" ]]; then 30 | run_upgrade 31 | fi 32 | log "*** Exiting and removing $UPDATE_FILE ***" 33 | rm $UPDATE_FILE 34 | fi 35 | 36 | log "*** Exiting... ***" 37 | --------------------------------------------------------------------------------