Skip to content

Commit

Permalink
record: Verify start.ts and support multiple output formats (fixes #2,
Browse files Browse the repository at this point in the history
  • Loading branch information
TheDrHax committed Oct 13, 2020
1 parent c416bba commit 8fb12c6
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 29 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,11 @@ Algorithm:
1. Check if channel is live and VOD for current stream already exists;
2. Get live VOD ID from Twitch API;
3. Start downloading live stream into file `VOD.end.ts`;
4. Wait 10 minutes and start downloading VOD into file `VOD.start.ts`;
5. Wait for both downloads to finish;
6. Concatenate two parts via `concat` script (see above).
4. Wait 1 minute and start downloading VOD into file `VOD.start.ts`;
5. Wait for VOD download to finish;
6. Check the possibility of concatenation and redownload VOD if timeline is not complete;
7. Wait for stream to finish;
8. Concatenate two parts via `concat` script (see above).

Note: Since Nov 2019 you have to provide your Twitch OAuth token in the command.
Otherwise the script will not be able to detect the ID of the live VOD and
Expand Down
14 changes: 8 additions & 6 deletions twitch_utils/concat.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,16 @@

import os
import sys
import json
import tempfile

from docopt import docopt
from subprocess import run, PIPE

from .clip import Clip
from .utils import tmpfile


class TimelineError(Exception):
pass


class Timeline(list):
Expand All @@ -43,7 +46,7 @@ def find_clip(clips: list, pos: float) -> Clip:
for clip in clips:
if clip.start <= pos and clip.end > pos:
return clip
raise Exception(f'Position {pos} is not present in any of clips')
raise TimelineError(f'Position {pos} is not present in any of clips')

def __init__(self, clips: list):
clips.sort(key=lambda k: k.start)
Expand Down Expand Up @@ -75,7 +78,7 @@ def ffconcat_map(self) -> str:
])

def render(self, path: str = 'full.mp4', container: str = 'mp4',
mp4_faststart: bool = True, force: bool = False) -> int:
mp4_faststart: bool = False, force: bool = False) -> int:
concat_map = self.ffconcat_map()

if path.endswith('.txt') or path == '-' and container == 'txt':
Expand All @@ -89,8 +92,7 @@ def render(self, path: str = 'full.mp4', container: str = 'mp4',

print(concat_map, file=sys.stderr)

map_file_name = os.path.join(tempfile.gettempdir(),
os.urandom(24).hex())
map_file_name = tmpfile('txt', '.')
map_file = open(map_file_name, 'w')
map_file.write(concat_map)
map_file.flush()
Expand Down
59 changes: 42 additions & 17 deletions twitch_utils/record.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Usage: twitch_utils record [options] --oauth=<token> [--] <channel> [<quality>]
"""Usage: twitch_utils record [options] --oauth=<token> [--] <channel> [<quality>] [-o <file>]
Parameters:
channel Name of the channel. Can be found in the URL: twitch.tv/<channel>
Expand All @@ -8,8 +8,10 @@
Options:
--oauth <token> Twitch OAuth token. You need to extract it from the site's
cookie named "auth-token".
-j <threads> Number of simultaneous downloads of live segments. This option
is passed to streamlink as --hls-segment-threads. [default: 4]
-o <name> Name of the output file. For more information see
`twitch_utils concat --help`.
-j <threads> Number of simultaneous downloads of live segments. [default: 4]
-y, --force Overwrite output file without confirmation.
--debug Forward output of streamlink and ffmpeg to stderr.
"""

Expand All @@ -31,7 +33,7 @@
from multiprocessing import Process

from .clip import Clip
from .concat import Timeline
from .concat import Timeline, TimelineError
from .twitch import TwitchAPI


Expand Down Expand Up @@ -82,6 +84,12 @@ def main(argv=None):
global DEBUG
DEBUG = args['--debug']

output = args['-o']

if output == '-':
print('ERR: This script does not support stdout as output.')
sys.exit(1)

api = TwitchAPI(args['--oauth'])

channel = args['<channel>']
Expand Down Expand Up @@ -122,34 +130,51 @@ def main(argv=None):
quality=stream.quality,
threads=1)

p_stream = Process(target=stream.download, args=(f'{v}.end.ts',))
p_vod = Process(target=vod.download, args=(f'{v}.start.ts',))

print('Starting to record the live stream...')
p_stream = Process(target=stream.download, args=(f'{v}.end.ts',))
p_stream.start()

sleep(600)
sleep(60)
print('Starting to download live VOD (beginning of the stream)...')
p_vod.start()
for i in range(3):
p_vod = Process(target=vod.download, args=(f'{v}.start.ts',))
p_vod.start()
p_vod.join()

print('Testing the possibility of concatenation')
try:
Timeline([Clip(f'{v}.{part}.ts') for part in ['start', 'end']])
except TimelineError as ex:
if i == 2:
print(ex)
print('ERR: Unable to download live VOD')
sys.exit(1)

print('Concatenation is not possible, redownloading live VOD...')
sleep(60)
p_vod.close()

print('Concatenation is possible, waiting for stream to end')
p_vod.close()
break

p_vod.join()
p_stream.join()

p_vod.close()
p_stream.close()

print('Download finished')
print('Trying to assemble downloaded segments into full stream...')
print('Stream ended')

try:
t = Timeline([Clip(f'{v}.{part}.ts') for part in ['start', 'end']])
except Exception as ex:
except TimelineError as ex:
print(ex)
print('ERR: Unable to concatenate segments!')
sys.exit(1)

print('Segments can be concatenated! Starting rendering...')
t.render(sys.argv[2] if len(sys.argv) == 3 else f'{v}.mp4')
if not output:
output = f'{v}.ts'

print(f'Writing stream recording to {output}')
t.render(output, force=args['--force'])

print('Cleaning up...')
[os.unlink(f'{v}.{part}.ts') for part in ['start', 'end']]
Expand Down
7 changes: 4 additions & 3 deletions twitch_utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import tempfile


def tmpfile(ext='tmp'):
return os.path.join(tempfile.gettempdir(),
os.urandom(24).hex() + '.' + ext)
def tmpfile(ext='tmp', path=None):
if not path:
path = tempfile.gettempdir()
return os.path.join(path, os.urandom(24).hex() + '.' + ext)

0 comments on commit 8fb12c6

Please sign in to comment.