mirror of
https://github.com/cyberboy666/obs_scheduler.git
synced 2025-07-05 06:00:34 -04:00
initial commit
This commit is contained in:
parent
fd92c3bec6
commit
cd2fb7a452
5 changed files with 290 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
schedule.json
|
||||||
|
obs_stream.log
|
207
obs_scheduler.py
Normal file
207
obs_scheduler.py
Normal file
|
@ -0,0 +1,207 @@
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
from flask import Flask, request, jsonify, render_template, redirect, url_for
|
||||||
|
import m3u8
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import obsws_python as obs
|
||||||
|
|
||||||
|
|
||||||
|
# Configure logging
|
||||||
|
logging.basicConfig(filename='obs_stream.log', level=logging.INFO, format='%(asctime)s:%(levelname)s:%(message)s')
|
||||||
|
|
||||||
|
# Flask setup
|
||||||
|
app = Flask(__name__)
|
||||||
|
schedule_file = 'schedule.json'
|
||||||
|
|
||||||
|
# OBS WebSocket connection configuration
|
||||||
|
host = 'localhost'
|
||||||
|
port = 4444
|
||||||
|
password = 'your_password'
|
||||||
|
|
||||||
|
# Global variables to control the streaming
|
||||||
|
stream_active = False
|
||||||
|
current_playlist = ""
|
||||||
|
schedule = []
|
||||||
|
stop_streaming = threading.Event()
|
||||||
|
stream_schedule = {} # To store multiple scheduled start/stop times
|
||||||
|
|
||||||
|
def parse_playlist(playlist_path):
|
||||||
|
playlist = m3u8.load(playlist_path)
|
||||||
|
videos = []
|
||||||
|
run_time = 0
|
||||||
|
for segment in playlist.segments:
|
||||||
|
# print(vars(segment))
|
||||||
|
videos.append({'title': '.'.join(segment.title.split('.')[:-1]), 'start_time': run_time})
|
||||||
|
run_time = run_time + segment.duration
|
||||||
|
return videos
|
||||||
|
|
||||||
|
def generate_schedule(start_time, playlist_path):
|
||||||
|
videos = parse_playlist(playlist_path)
|
||||||
|
schedule = []
|
||||||
|
current_time = start_time
|
||||||
|
for video in videos:
|
||||||
|
end_time = current_time + timedelta(seconds=video['duration'])
|
||||||
|
schedule.append({'title': video['title'], 'start_time': current_time, 'end_time': end_time})
|
||||||
|
current_time = end_time
|
||||||
|
return schedule
|
||||||
|
|
||||||
|
def start_stream(playlist_path, stream_name):
|
||||||
|
global stream_active, current_playlist, schedule
|
||||||
|
try:
|
||||||
|
# Initialize the OBS WebSocket client using ReqClient (connects automatically)
|
||||||
|
cl = obs.ReqClient(host=host, port=port, password=password, timeout=3)
|
||||||
|
|
||||||
|
# get base_width and base_height
|
||||||
|
video_settings = cl.get_video_settings()
|
||||||
|
base_width = video_settings.base_width
|
||||||
|
base_height = video_settings.base_height
|
||||||
|
|
||||||
|
scene_name = stream_name # Name of the scene in OBS
|
||||||
|
|
||||||
|
# get existing scenes
|
||||||
|
raw_list = cl.get_scene_list()
|
||||||
|
scene_list = [a['sceneName'] for a in raw_list.scenes]
|
||||||
|
|
||||||
|
# if doesnt already exist create scene with this name
|
||||||
|
if scene_name not in scene_list:
|
||||||
|
cl.create_scene(scene_name)
|
||||||
|
cl.set_current_program_scene(scene_name)
|
||||||
|
|
||||||
|
input_name = scene_name + " - vlc playlist" # Name for the media source in OBS
|
||||||
|
|
||||||
|
raw_list = cl.get_scene_item_list(scene_name)
|
||||||
|
scene_item_list = [a['sourceName'] for a in raw_list.scene_items]
|
||||||
|
|
||||||
|
# if source doesnt already exist create source with this name
|
||||||
|
if input_name not in scene_item_list:
|
||||||
|
full_playlist_path = playlist_path
|
||||||
|
input_kind = "vlc_source" # Kind of input (VLC source)
|
||||||
|
input_settings = {
|
||||||
|
"playlist": [{"hidden": False,"selected": False,"value": full_playlist_path}]
|
||||||
|
}
|
||||||
|
scene_item_enabled = True # Set to True to enable the scene item
|
||||||
|
# Create the VLC media source input
|
||||||
|
settings = cl.create_input(scene_name, input_name, input_kind, input_settings, scene_item_enabled)
|
||||||
|
|
||||||
|
# transform -> fit to screen
|
||||||
|
scene_item_id = cl.get_scene_item_id(scene_name, input_name, offset=None).scene_item_id
|
||||||
|
cl.set_scene_item_transform(scene_name, scene_item_id, {'boundsWidth': base_width, 'boundsHeight': base_height, 'boundsType': 'OBS_BOUNDS_SCALE_INNER'})
|
||||||
|
|
||||||
|
|
||||||
|
# Start the stream
|
||||||
|
logging.info("Starting stream with playlist: {}".format(playlist_path))
|
||||||
|
current_playlist = playlist_path
|
||||||
|
start_time = datetime.now()
|
||||||
|
schedule = generate_schedule(start_time,playlist_path)
|
||||||
|
cl.start_stream()
|
||||||
|
stream_active = True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"OBS Error: {e}")
|
||||||
|
finally:
|
||||||
|
cl.disconnect()
|
||||||
|
|
||||||
|
def stop_stream():
|
||||||
|
global stream_active
|
||||||
|
try:
|
||||||
|
# Initialize the OBS WebSocket client using ReqClient (connects automatically)
|
||||||
|
cl = obs.ReqClient(host=host, port=port, password=password, timeout=3)
|
||||||
|
|
||||||
|
# Stop the stream
|
||||||
|
logging.info("Stopping stream")
|
||||||
|
cl.stop_stream()
|
||||||
|
stream_active = False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"OBS Error: {e}")
|
||||||
|
finally:
|
||||||
|
cl.disconnect()
|
||||||
|
|
||||||
|
def scheduled_stream():
|
||||||
|
while not stop_streaming.is_set():
|
||||||
|
current_time = datetime.now()
|
||||||
|
for key, times in stream_schedule.items():
|
||||||
|
start_time = datetime.strptime(times['start_time'], '%Y-%m-%dT%H:%M')
|
||||||
|
stop_time = datetime.strptime(times['stop_time'], '%Y-%m-%dT%H:%M')
|
||||||
|
playlist_path = times['playlist']
|
||||||
|
stream_name = times['stream_name']
|
||||||
|
|
||||||
|
if start_time <= current_time < stop_time and not stream_active:
|
||||||
|
start_stream(playlist_path, stream_name)
|
||||||
|
elif current_time >= stop_time and stream_active:
|
||||||
|
stop_stream()
|
||||||
|
time.sleep(60) # Check every minute
|
||||||
|
|
||||||
|
def load_schedule():
|
||||||
|
global schedule
|
||||||
|
if os.path.exists(schedule_file):
|
||||||
|
with open(schedule_file, 'r') as f:
|
||||||
|
schedule = json.load(f)
|
||||||
|
|
||||||
|
def save_schedule():
|
||||||
|
with open(schedule_file, 'w') as f:
|
||||||
|
json.dump(schedule, f, indent=4)
|
||||||
|
|
||||||
|
def get_obs_state():
|
||||||
|
obs_state = {
|
||||||
|
'obs_running': False,
|
||||||
|
'streaming': False,
|
||||||
|
'recording': False
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
cl = obs.ReqClient(host=host, port=port, password=password, timeout=3)
|
||||||
|
obs_state.update({
|
||||||
|
'obs_running': True,
|
||||||
|
'streaming': cl.get_stream_status().output_active,
|
||||||
|
'recording': cl.get_record_status().output_active
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Unexpected error: {e}")
|
||||||
|
return obs_state
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
load_schedule()
|
||||||
|
obs_state = get_obs_state()
|
||||||
|
return render_template('index.html', current_playlist=current_playlist, schedule=schedule, obs_state=obs_state)
|
||||||
|
|
||||||
|
@app.route('/schedule', methods=['POST'])
|
||||||
|
def schedule_stream():
|
||||||
|
stream_name = request.form.get('stream_name')
|
||||||
|
start_time = request.form.get('start_time')
|
||||||
|
stop_time = request.form.get('stop_time')
|
||||||
|
playlist_path = request.form.get('playlist')
|
||||||
|
program = parse_playlist(playlist_path)
|
||||||
|
timestamp = datetime.strptime(start_time, '%Y-%m-%dT%H:%M')
|
||||||
|
program = [{'title': x['title'], 'start_time': (timestamp + timedelta(seconds=x['start_time'])).strftime('%Y-%m-%dT%H:%M')} for x in program]
|
||||||
|
|
||||||
|
key = f"{start_time}-{stop_time}"
|
||||||
|
stream_schedule[key] = {
|
||||||
|
'stream_name': stream_name,
|
||||||
|
'start_time': start_time,
|
||||||
|
'stop_time': stop_time,
|
||||||
|
'playlist': playlist_path,
|
||||||
|
'program': program
|
||||||
|
}
|
||||||
|
schedule.append({
|
||||||
|
'stream_name': stream_name,
|
||||||
|
'start_time': start_time,
|
||||||
|
'stop_time': stop_time,
|
||||||
|
'playlist': playlist_path,
|
||||||
|
'program': program
|
||||||
|
})
|
||||||
|
save_schedule()
|
||||||
|
print(url_for('index'))
|
||||||
|
return redirect(url_for('index'))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Load existing schedule
|
||||||
|
load_schedule()
|
||||||
|
# Start the scheduled streaming thread
|
||||||
|
threading.Thread(target=scheduled_stream, daemon=True).start()
|
||||||
|
# Run the Flask app
|
||||||
|
app.run(host='0.0.0.0', port=5000)
|
3
requirements.txt
Normal file
3
requirements.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
Flask
|
||||||
|
m3u8
|
||||||
|
obsws-python
|
17
static/style.css
Normal file
17
static/style.css
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
}
|
||||||
|
h1, h2 {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
table, th, td {
|
||||||
|
border: 1px solid black;
|
||||||
|
}
|
||||||
|
th, td {
|
||||||
|
padding: 8px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
61
templates/index.html
Normal file
61
templates/index.html
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>OBS Scheduler</title>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>OBS Scheduler</h1>
|
||||||
|
|
||||||
|
<h2>OBS Status</h2>
|
||||||
|
<p>OBS Running: {{ 'Yes' if obs_state['obs_running'] else 'No' }}</p>
|
||||||
|
<p>Streaming: {{ 'Yes' if obs_state['streaming'] else 'No' }}</p>
|
||||||
|
<p>Current Playlist: {{ current_playlist }}</p>
|
||||||
|
|
||||||
|
<h2>Schedule Stream</h2>
|
||||||
|
<form action="/schedule" method="post" enctype="multipart/form-data">
|
||||||
|
<label for="stream_name">Stream Name:</label>
|
||||||
|
<input type="text" id="stream_name" name="stream_name" required><br><br>
|
||||||
|
<label for="start_time">Start Time:</label>
|
||||||
|
<input type="datetime-local" id="start_time" name="start_time" required><br><br>
|
||||||
|
<label for="stop_time">Stop Time:</label>
|
||||||
|
<input type="datetime-local" id="stop_time" name="stop_time" required><br><br>
|
||||||
|
<label for="playlist">Playlist File:</label>
|
||||||
|
<input type="text" id="playlist" name="playlist" required><br><br>
|
||||||
|
<button type="submit">Schedule Stream</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
<h2>Schedule</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Stream Name</th>
|
||||||
|
<th>Start Time</th>
|
||||||
|
<th>End Time</th>
|
||||||
|
<th>Playlist File</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for item in schedule %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ item['stream_name'] }}</td>
|
||||||
|
<td>{{ item['start_time'] }}</td>
|
||||||
|
<td>{{ item['stop_time'] }}</td>
|
||||||
|
<td>{{ item['playlist'] }}</td>
|
||||||
|
<td><details><summary>program times</summary>
|
||||||
|
<ul>
|
||||||
|
{% for x in item['program'] %}
|
||||||
|
<li><b>{{ x['start_time'] }}</b> - {{ x['title'] }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Add table
Add a link
Reference in a new issue