add channel ID, expand readme
This commit is contained in:
parent
bfa12f842e
commit
949f576fec
40
README.md
40
README.md
@ -1,6 +1,39 @@
|
|||||||
# owncast-clipper
|
# owncast-clipper
|
||||||
WIP: a bot that listens for clip requests from owncast chat and uploads to a configured peertube instance.
|
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
|
## Example docker-compose.yml
|
||||||
```yaml
|
```yaml
|
||||||
version: '3'
|
version: '3'
|
||||||
@ -29,6 +62,13 @@ services:
|
|||||||
- PEERTUBE_HOST=https://video.peertube.site # your PeerTube server
|
- PEERTUBE_HOST=https://video.peertube.site # your PeerTube server
|
||||||
- PEERTUBE_USER=your_peertube_username
|
- PEERTUBE_USER=your_peertube_username
|
||||||
- PEERTUBE_PASS=SuperSecretPassword
|
- PEERTUBE_PASS=SuperSecretPassword
|
||||||
|
- PEERTUBE_CHANNEL_ID=1502
|
||||||
- OWNCAST_HOST=http://owncast:8080 # internal or fully-qualified Owncast server
|
- OWNCAST_HOST=http://owncast:8080 # internal or fully-qualified Owncast server
|
||||||
- OWNCAST_AUTH=AReallyLongOwncastAccessTokenString # Owncast access token with chat permission
|
- 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
|
||||||
|
@ -6,4 +6,4 @@ WEBSOCKET_INITIAL_BACKOFF = 5.0
|
|||||||
CLIP_DIRECTORY, TEMP_DIRECTORY, CLIP_QUANTITY = None, None, None
|
CLIP_DIRECTORY, TEMP_DIRECTORY, CLIP_QUANTITY = None, None, None
|
||||||
PEERTUBE_HOST, PEERTUBE_USER, PEERTUBE_PASS = None, None, None
|
PEERTUBE_HOST, PEERTUBE_USER, PEERTUBE_PASS = None, None, None
|
||||||
OWNCAST_HOST, OWNCAST_AUTH = 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
|
||||||
|
@ -65,6 +65,27 @@ def merge_segments_to_mp4(segments: list[str], output_path: Path, output_filenam
|
|||||||
return output_file
|
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):
|
def generate_and_post_clip(user: str, title: str, timestamp: datetime):
|
||||||
"""
|
"""
|
||||||
Invokes clip collection, remux, upload, and notification (chat) routines.
|
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
|
None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
recent_clips = get_recent_segments(
|
if not (recent_clips := get_recent_segments(config.CLIP_DIRECTORY, config.CLIP_QUANTITY)):
|
||||||
config.CLIP_DIRECTORY, config.CLIP_QUANTITY)
|
return
|
||||||
new_filename = f'{timestamp.strftime("%Y%m%d_%H%M%S_%f")}.mp4'
|
new_filename = f'{timestamp.strftime("%Y%m%d_%H%M%S_%f")}.mp4'
|
||||||
new_filepath = merge_segments_to_mp4(
|
new_filepath = merge_segments_to_mp4(
|
||||||
recent_clips, config.TEMP_DIRECTORY, new_filename)
|
recent_clips, config.TEMP_DIRECTORY, new_filename)
|
||||||
|
|
||||||
if (token := authenticate_to_peertube(config.PT_API_BASE, config.PEERTUBE_USER, config.PEERTUBE_PASS)) is None:
|
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)
|
print(f'Failed to authenticate to PeerTube', file=sys.stderr)
|
||||||
|
delete_clip(new_filename)
|
||||||
return
|
return
|
||||||
# TODO: handle this gracefully
|
|
||||||
|
|
||||||
stream_title = ''
|
stream_title = ''
|
||||||
if (stream_title := get_owncast_title()):
|
if (stream_title := get_owncast_title()):
|
||||||
@ -97,10 +118,11 @@ def generate_and_post_clip(user: str, title: str, timestamp: datetime):
|
|||||||
upload_options[
|
upload_options[
|
||||||
'description'] = f'🎞️ Clipped by **{user}** {stream_title} on {timestamp.strftime("%Y-%m-%d %H:%M:%S")}'
|
'description'] = f'🎞️ Clipped by **{user}** {stream_title} on {timestamp.strftime("%Y-%m-%d %H:%M:%S")}'
|
||||||
upload_options['description'] += f'\n🌐 {config.OWNCAST_HOST}'
|
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)
|
print(f'Failed to upload file to PeerTube', file=sys.stderr)
|
||||||
|
delete_clip(new_filename)
|
||||||
return
|
return
|
||||||
# TODO: handle this gracefully
|
delete_clip(new_filename)
|
||||||
|
|
||||||
clip_url = f'{config.PEERTUBE_HOST}{video_url}'
|
clip_url = f'{config.PEERTUBE_HOST}{video_url}'
|
||||||
message = f'**{title}**: {clip_url}'
|
message = f'**{title}**: {clip_url}'
|
||||||
|
1
main.py
1
main.py
@ -24,6 +24,7 @@ def main():
|
|||||||
config.PEERTUBE_HOST = os.environ['PEERTUBE_HOST'].rstrip('/')
|
config.PEERTUBE_HOST = os.environ['PEERTUBE_HOST'].rstrip('/')
|
||||||
config.PEERTUBE_USER = os.environ['PEERTUBE_USER']
|
config.PEERTUBE_USER = os.environ['PEERTUBE_USER']
|
||||||
config.PEERTUBE_PASS = os.environ['PEERTUBE_PASS']
|
config.PEERTUBE_PASS = os.environ['PEERTUBE_PASS']
|
||||||
|
config.PEERTUBE_CHANNEL_ID = os.environment['PEERTUBE_CHANNEL_ID']
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
print(
|
print(
|
||||||
f'Error: PeerTube host and credentials must be specified: {e}', file=sys.stderr)
|
f'Error: PeerTube host and credentials must be specified: {e}', file=sys.stderr)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user