add channel ID, expand readme

This commit is contained in:
Glyphscribe Ety 2025-04-27 12:11:34 -05:00
parent bfa12f842e
commit 949f576fec
4 changed files with 72 additions and 9 deletions

View File

@ -1,6 +1,39 @@
# owncast-clipper
WIP: a bot that listens for clip requests from owncast chat and uploads to a configured peertube instance.
## ✅ Preparation
Beyond easily-discoverable information such as user credentials and base URLs, there are two pieces of information needed which non-technical users may struggle with: the Owncast Access Token and the PeerTube channel ID.
### Owncast Access Token
Can be found by visiting the `Integrations -> Acess Tokens` page of the Owncast Admin at `admin/access-tokens/`:
1. Visit the Access Tokens page of your Owncast Admin interface. If your instance is `https://stream.person.site`, the page may be found at `https://stream.person.site/admin/access-token`.
2. You'll need to Create an Access Token and name it appropriately (something like "Clip Bot" is a good starting point). It will require the "Can send chat messages on behalf of the owner of this token" permission.
3. Once the token is created, you'll be able to retrieve it by clicking on the eye icon to unhide the token, then copying and pasting into the `OWNCAST_AUTH` variable as described [below](#⚙️-configuration).
### PeerTube Channel ID
This one is a little trickier to get, because the value that we need isn't directly exposed via the UI. Here's what to do:
1. Go to your Channel (not _User_) page on PeerTube. If your PeerTube instance is `https://peertube.video.site` and your channel name is `Cool Channel`, you should have a URL like this: `https://peertube.video.site/c/cool_channel/videos`.
2. You want the part of the URL between the `/c/` and `/videos`: in this case: `cool_channel`.
3. Adjust the following URL to match your environment and open it in your browser: `https://peertube.video.site/api/v1/video-channels/cool_channel`.
4. Your browser may or may not format the raw data, but you're looking for the _first_ instance of "id" in the response. If your browser is showing you raw data, you can just do a text search for "id".
5. The number immediately following "id" is your Channel ID! Copy and paste it into your `PEERTUBE_CHANNEL_ID` variable as described [below](#⚙️-configuration).
## ⚙️ Configuration
At the moment, configuration is performed exclusively through environment variables.
| Variable | Description | Required |
| :------- | :---------- | :------: |
| PEERTUBE_HOST | The base URL for the PeerTube server we're uploading to, e.g. https://peertube.video.site | **Yes** |
| PEERTUBE_USER | The username of the account authorized to post to PeerTube | **Yes** |
| PEERTUBE_PASS | The password of the account authorized to post to PeerTube | **Yes** |
| PEERTUBE_CHANNEL_ID | The internal "channel id" of the PeerTube channel we're uploading clips to. Described [above](#peertube-channel-id). | **Yes** |
| OWNCAST_HOST | The base URL for the PeerTube server we're making API calls against, e.g. `https://coolstream.site` or `http://app:8080` | **Yes** |
| OWNCAST_AUTH | A valid Owncast Access Token, with access to the User Chat scope. Described [above](#owncast-access-token). | **Yes** |
| CLIP_DIRECTORY | The directory where source .ts files from owncast will be collected. /var/data/hls/0/ by default. | No |
| TEMP_DIRECTORY | The directory where intermediate remuxed .mp4 files will be stored. /tmp/ by default. | No |
| CLIP_QUANTITY | The maximum number of segment files to attempt to collect to make a clip. 20 by default. | No |
## Example docker-compose.yml
```yaml
version: '3'
@ -29,6 +62,13 @@ services:
- PEERTUBE_HOST=https://video.peertube.site # your PeerTube server
- PEERTUBE_USER=your_peertube_username
- PEERTUBE_PASS=SuperSecretPassword
- PEERTUBE_CHANNEL_ID=1502
- OWNCAST_HOST=http://owncast:8080 # internal or fully-qualified Owncast server
- OWNCAST_AUTH=AReallyLongOwncastAccessTokenString # Owncast access token with chat permission
```
```
CLIP_DIRECTORY, TEMP_DIRECTORY, CLIP_QUANTITY = None, None, None
PEERTUBE_HOST, PEERTUBE_USER, PEERTUBE_PASS = None, None, None
OWNCAST_HOST, OWNCAST_AUTH = None, None
AUTHENTICATION_REQUIRED, PT_API_BASE, PEERTUBE_CHANNEL_ID = None, None, None

View File

@ -6,4 +6,4 @@ WEBSOCKET_INITIAL_BACKOFF = 5.0
CLIP_DIRECTORY, TEMP_DIRECTORY, CLIP_QUANTITY = None, None, None
PEERTUBE_HOST, PEERTUBE_USER, PEERTUBE_PASS = None, None, None
OWNCAST_HOST, OWNCAST_AUTH = None, None
AUTHENTICATION_REQUIRED, PT_API_BASE = None, None
AUTHENTICATION_REQUIRED, PT_API_BASE, PEERTUBE_CHANNEL_ID = None, None, None

View File

@ -65,6 +65,27 @@ def merge_segments_to_mp4(segments: list[str], output_path: Path, output_filenam
return output_file
def delete_clip(filename: str):
"""
Deletes a named file from the temporary directory
Args:
filename (str): The name of the file to be deleted, e.g. file.mp4
Returns:
None
"""
file = config.TEMP_DIRECTORY / filename
try:
file.unlink()
except FileNotFoundError:
print(f'Error deleting temporary file: {file}; file not found.')
except Exception as e:
print(f'Error deleting temporary file: {file}; {e}.')
return
def generate_and_post_clip(user: str, title: str, timestamp: datetime):
"""
Invokes clip collection, remux, upload, and notification (chat) routines.
@ -78,16 +99,16 @@ def generate_and_post_clip(user: str, title: str, timestamp: datetime):
None
"""
recent_clips = get_recent_segments(
config.CLIP_DIRECTORY, config.CLIP_QUANTITY)
if not (recent_clips := get_recent_segments(config.CLIP_DIRECTORY, config.CLIP_QUANTITY)):
return
new_filename = f'{timestamp.strftime("%Y%m%d_%H%M%S_%f")}.mp4'
new_filepath = merge_segments_to_mp4(
recent_clips, config.TEMP_DIRECTORY, new_filename)
if (token := authenticate_to_peertube(config.PT_API_BASE, config.PEERTUBE_USER, config.PEERTUBE_PASS)) is None:
print(f'Failed to authenticate to PeerTube', file=sys.stderr)
delete_clip(new_filename)
return
# TODO: handle this gracefully
stream_title = ''
if (stream_title := get_owncast_title()):
@ -97,14 +118,15 @@ def generate_and_post_clip(user: str, title: str, timestamp: datetime):
upload_options[
'description'] = f'🎞️ Clipped by **{user}** {stream_title} on {timestamp.strftime("%Y-%m-%d %H:%M:%S")}'
upload_options['description'] += f'\n🌐 {config.OWNCAST_HOST}'
if (video_url := upload_to_peertube(config.PT_API_BASE, token, new_filepath, 3800, title, **upload_options)) is None:
if (video_url := upload_to_peertube(config.PT_API_BASE, token, new_filepath, config.PEERTUBE_CHANNEL_ID, title, **upload_options)) is None:
print(f'Failed to upload file to PeerTube', file=sys.stderr)
delete_clip(new_filename)
return
# TODO: handle this gracefully
delete_clip(new_filename)
clip_url = f'{config.PEERTUBE_HOST}{video_url}'
message = f'**{title}**: {clip_url}'
send_owncast_message(message)
print(f'Clip created by {user} at {timestamp}: {clip_url}')
return

View File

@ -24,6 +24,7 @@ def main():
config.PEERTUBE_HOST = os.environ['PEERTUBE_HOST'].rstrip('/')
config.PEERTUBE_USER = os.environ['PEERTUBE_USER']
config.PEERTUBE_PASS = os.environ['PEERTUBE_PASS']
config.PEERTUBE_CHANNEL_ID = os.environment['PEERTUBE_CHANNEL_ID']
except KeyError as e:
print(
f'Error: PeerTube host and credentials must be specified: {e}', file=sys.stderr)