Example #1
0
class Downloader:
    def __init__(self, error_messages, packager, p_tokens_file):
        self.__error_messages = error_messages
        self.__packager = packager
        self.__p_tokens_file = p_tokens_file

        self.__logger = Logger()

    def commander(self, command, args):
        '''
            download (package name[0]):
                downloads clips from given package
        '''
        if command == 'download':
            filter = self.__filter(args, 1)
            if filter:
                name = args[0]
                package = self.__packager.get(name)

                for streamer in package.get_data()['streamers']:
                    self.__download(streamer, package)

            return True
        return False

    def __filter(self, args, ammount):
        if len(args) >= ammount:
            return True
        else:
            print(ConsoleColors.RED +
                  'Error: {0}'.format(self.__error_messages[0]) +
                  ConsoleColors.RESET)

    def __check_paths(self, path):
        if not os.path.exists(self.__p_packages_file):
            os.mkdir(path)

    def __download(self, streamer, package):
        with open(self.__p_tokens_file, 'r') as f:
            client_id = json.load(f)['twitch']['client_id']

        access_token, broad_id = Downloader.check_username(streamer).split("&")

        if package.get_data()['period'] == 'week':
            start = generate(datetime.utcnow().replace(tzinfo=pytz.utc) -
                             timedelta(days=7))
            end = generate(datetime.utcnow().replace(tzinfo=pytz.utc))
        elif package.get_data()['period'] == 'month':
            start = generate(datetime.utcnow().replace(tzinfo=pytz.utc) -
                             timedelta(days=30))
            end = generate(datetime.utcnow().replace(tzinfo=pytz.utc))

        count = 0
        pagination = ''
        times = list()
        game_ids = dict()
        while count < int(package.get_data()['limit']):
            r = requests.get(
                'https://api.twitch.tv/helix/clips?broadcaster_id={}&first={}&started_at={}&ended_at={}&after={}'
                .format(broad_id,
                        package.get_data()['limit'], start, end, pagination),
                headers={
                    'Authorization': 'Bearer ' + access_token,
                    'Client-ID': client_id
                })

            clips = r.json()['data']
            if len(clips) > 0:
                for clip in clips:
                    if type(clip) == dict:
                        if clip['game_id'] not in game_ids:
                            game_ids[clip['game_id']] = 1
                        else:
                            game_ids[clip['game_id']] += 1

                        thumbnail_url = clip['thumbnail_url']

                        mp4_url = thumbnail_url.split('-preview',
                                                      1)[0] + '.mp4'

                        number = count + 1
                        if len(str(number)) == 1:
                            number = '0' + str(number)

                        mp4_name = str(
                            Path(package.get_data()['clips_folder']) /
                            str(str(number) + '.mp4'))

                        self.__logger.log("Downloading: " + str(mp4_name))

                        res = requests.get(mp4_url)
                        with open(mp4_name, 'wb') as f:
                            f.write(res.content)

                        cmd = 'ffprobe -show_entries format=duration -v quiet -of csv="p=0" ' + mp4_name
                        duration = float(os.popen(cmd).read())
                        created_at = datetime.strptime(clip['created_at'],
                                                       '%Y-%m-%dT%H:%M:%SZ')
                        ended_at = created_at + timedelta(0, duration)

                        exist = False
                        if len(times) > 0:
                            for i in range(len(times)):
                                if times[i]['created_at'] < created_at < times[
                                        i]['ended_at'] or times[i][
                                            'created_at'] < ended_at < times[
                                                i]['ended_at']:
                                    exist = True
                                    break
                                else:
                                    time_meta = {
                                        'created_at': created_at,
                                        'ended_at': ended_at
                                    }
                                    times.append(time_meta)
                        else:
                            time_meta = {
                                'created_at': created_at,
                                'ended_at': ended_at
                            }
                            times.append(time_meta)

                        if exist:
                            os.remove(mp4_name)
                        else:
                            count += 1
                            if count >= int(package.get_data()['limit']):
                                break

                pagination = r.json()['pagination']['cursor']
            else:
                raise Exception('Clips not found')

        games_sorted = sorted(game_ids.items(),
                              key=lambda x: x[1],
                              reverse=True)

        stop = 1
        if len(games_sorted) > 1:
            stop = 2

        games = list()
        for i in range(stop):
            r = requests.get('https://api.twitch.tv/helix/games?id=' +
                             games_sorted[i][0],
                             headers={
                                 'Authorization': 'Bearer ' + access_token,
                                 'Client-ID': client_id
                             })

            games.append(r.json()['data'][0]['name'])

        package.get_data()['additional_info']['games'] = games
        package.update()
        self.__logger.separator()

    @staticmethod
    def check_username(username):
        with open(Path('tokens/tokens.json'), 'r') as f:
            credentials = json.load(f)['twitch']

        try:
            r = requests.post(
                'https://id.twitch.tv/oauth2/token?client_id={0}&client_secret={1}&grant_type=client_credentials'
                .format(credentials['client_id'],
                        credentials['client_secret']))
            access_token = r.json()['access_token']

            r = requests.get('https://api.twitch.tv/helix/users?login='******'Authorization': 'Bearer ' + access_token,
                                 'Client-ID': credentials['client_id']
                             })
            broad_id = r.json()['data'][0]['id']

            if not broad_id:
                print('Bad request, check username or user banned')
                return False
        except:
            print('Failed to connect to Twitch API')
            return False

        return access_token + '&' + broad_id
Example #2
0
class Editor:
    def __init__(self, error_messages, packager, p_videos_media):
        self.__error_messages = error_messages
        self.__packager = packager
        self.__p_videos_media = p_videos_media

        self.__logger = Logger()

    def commander(self, command, args):
        '''
            edit (package name[0]):
                concatenates all clips + outro, then inserts video intro at the beginning
        '''
        if command == 'edit':
            filter = self.__filter(args, 1)
            if filter:
                name = args[0]
                package = self.__packager.get(name)

                if not package:
                    return True

                p_clips = package.get_data()['clips_folder']
                p_output = package.get_data()['output_folder']
                concat_output = Path(p_output) / 'concat.txt'

                if os.path.exists(concat_output):
                    os.remove(concat_output)

                with open(concat_output, 'x') as f:
                    for subdir, dirs, files in os.walk(p_clips):
                        files.sort()
                        for file in files:
                            mp4_file = Path(p_clips) / file
                            f.write('file ' + str(mp4_file) + '\n')
                    f.write('file ' + str(self.__p_videos_media / 'outro.mp4'))

                date = datetime.now()
                output_video = Path(p_output) / str(name + date.strftime('%Y-%m-%d') + '.mp4')
                self.__generate_video(package, concat_output, output_video)
                self.__logger.log('video {} created'.format(output_video))
                self.__logger.separator()
            return True
        else:
            return False
    
    def __filter(self, args, ammount):
        if len(args) >= ammount:
            return True
        else:
            print(ConsoleColors.RED + 'Error: {0}'.format(self.__error_messages[0]) + ConsoleColors.RESET)

    def __generate_video(self, p, of, ov):
        cmd = 'ffmpeg -f concat -safe 0 -i {} -c copy {}'.format(of, ov)
        r = subprocess.run(cmd, shell=True, capture_output=True)
        
        intro = Path(self.__p_videos_media) / 'intro.mov'
        final_ov = str(ov).replace('mp4', '') + 'final.mp4'
        cmd = 'ffmpeg -i {} -i {} -filter_complex "[1:v]setpts=PTS-0/TB[a]; [0:v][a]overlay=enable=gte(t\,0):eof_action=pass[out]; [0][1]amix[a]" -map [out] -map [a] -c:v libx264 -crf 18 -pix_fmt yuv420p {}'.format(ov, intro, final_ov)
        r = subprocess.run(cmd, shell=True, capture_output=True)

        p.get_data()['additional_info']['output_video'] = str(final_ov)
        p.update()

        '''
        create twitter_video
        '''
        twitter_video = 'test.mp4'
        p.get_data()['additional_info']['twitter_video'] = twitter_video
        p.update()
Example #3
0
class Encoder:
    __DEFAULT = {
        'FPS': '60/1',
        'WIDTH': '1920',
        'HEIGHT': '1080',
        'TBN': '1/15360',
        'HZ': '1/44100'
    }

    def __init__(self, error_messages, packager):
        self.__error_messages = error_messages
        self.__packager = packager

        self.__logger = Logger()

    def commander(self, command, args):
        '''
            encode (package name[0]):
                encodes clips to make all clips has the same parameters options
        '''
        if command == 'encode':
            filter = self.__filter(args, 1)
            if filter:
                name = args[0]
                package = self.__packager.get(name)

                if not package:
                    return True

                folder = package.get_data()['clips_folder']
                for subdir, dirs, files in os.walk(folder):
                    files.sort()
                    for file in files:
                        mp4_file = Path(folder) / file
                        opt = self.__check_video(mp4_file)
                        if opt == '':
                            continue
                        else:
                            self.__logger.log('Re-encoding video {}'.format(mp4_file))
                            cmd = 'ffmpeg -i ' + str(mp4_file) + opt + str(mp4_file).replace('.mp4', '') + 'converted.mp4'
                            r = subprocess.run(cmd, capture_output=True, shell=True)

                            os.remove(mp4_file)
                self.__logger.separator()
            return True
        else:
            return False
    
    def __filter(self, args, ammount):
        if len(args) >= ammount:
            return True
        else:
            print(ConsoleColors.RED + 'Error: {0}'.format(self.__error_messages[0]) + ConsoleColors.RESET)

    def __check_video(self, video):
        cmd = 'ffprobe -v error -of json -show_entries stream=time_base,r_frame_rate,width,height ' + str(video)
        r = os.popen(cmd).read()
        j_streams = json.loads(r)['streams']

        streams = list([{}, {}])
        for j_stream in j_streams:
            if 'width' in j_stream.keys():
                streams[0] = j_stream
            else:
                streams[1] = j_stream

        opt = ''
        if str(streams[0]['width']) != self.__DEFAULT['WIDTH']:
            opt = opt + ' -vf scale=' + self.__DEFAULT['WIDTH'] + ':' + self.__DEFAULT['HEIGHT']
            opt = opt + ' -video_track_timescale ' + self.__DEFAULT['TBN'][2:]
            opt = opt + ' -r ' + self.__DEFAULT['FPS'][:-2]
        else:
            if streams[0]['time_base'] != self.__DEFAULT['TBN']:
                opt = opt + ' -video_track_timescale ' + self.__DEFAULT['TBN'][2:]
            if streams[0]['r_frame_rate'] != self.__DEFAULT['FPS']:
                opt = opt + ' -r ' + self.__DEFAULT['FPS'][:-2]

        if streams[1]['time_base'] != self.__DEFAULT['HZ']:
            opt = opt + ' -ar ' + self.__DEFAULT['HZ'][2:]

        if opt != '':
            opt = opt + ' '

        return opt
Example #4
0
class Uploader:
    def __init__(self, error_mesages, packager, p_tokens, p_videos_media):
        self.__error_messages = error_mesages
        self.__packager = packager

        self.__p_request_token = p_tokens / 'youtube/request.token'
        self.__p_client_secrets = p_tokens / 'youtube/client_secrets.json'
        self.__p_videos_metada = p_videos_media / 'metadata.json'

        self.__logger = Logger()

    def commander(self, command, args):
        '''
            upload (package name[0]):
                uploads video to google storage and youtube depending on package settings
        '''
        if command == 'upload':
            filter = self.__filter(args, 1)
            if filter:
                name = args[0]
                package = self.__packager.get(name)

                if not package:
                    return True

                if package.get_data()['upload_google']:
                    self.__upload_google(package)

                if package.get_data()['upload_youtube']:
                    self.__upload_youtube(package)

            return True
        else:
            return False

    def __filter(self, args, ammount):
        if len(args) >= ammount:
            return True
        else:
            print(ConsoleColors.RED +
                  'Error: {0}'.format(self.__error_messages[0]) +
                  ConsoleColors.RESET)

    def __upload_google(self, package):
        cmd = 'gsutil cp {} gs://clipperstorage/output/'.format(
            package.get_data()['additional_info']['output_video'])
        r = subprocess.run(cmd, shell=True, capture_output=True)
        self.__logger.log('Video {} uploaded to google storage'.format(
            package.get_data()['additional_info']['output_video']))
        self.__logger.separator()

    def __upload_youtube(self, package):
        with open(self.__p_videos_metada, 'r') as f:
            lang = package.get_data()['language']

            for md in json.loads(f.read())['video_metadata']:
                if md['language'] == lang:
                    data = md

        number = str(random.randint(1, 15))
        thumbnail = Path(
            package.get_data()['thumbanils_folder']) / str(number + '.png')

        name = package.get_data()['streamers'][0]
        meta = {
            'title':
            data['title'].format(name),
            'description':
            data['description'].format(name,
                                       package.get_data()['twitch_urls'][0]),
            'privacyStatus':
            'public',
            'playlistTitles': [data['playlist'].format(name)],
            'tags':
            data['tags'].format(name).split(','),
            'language':
            data['language']
        }

        for game in package.get_data()['additional_info']['games']:
            meta['tags'].append(game)
            meta['tags'].append(game + ' ' + name)

        meta_file = Path(
            package.get_data()['output_folder']) / 'meta_file.json'
        if os.path.exists(meta_file):
            os.remove(meta_file)

        with open(meta_file, 'x') as f:
            json.dump(meta, f)

        cmd = './youtubeuploader -metaJSON {0} -thumbnail {1} -filename {2} -cache {3} -secrets {4}'.format(
            meta_file, thumbnail,
            package.get_data()['additional_info']['output_video'],
            self.__p_request_token, self.__p_client_secrets)
        r = subprocess.run(cmd, shell=True, capture_output=True)

        output = r.stdout
        error = str(r.stderr)
        if 'quota' in error:
            self.__logger.log(
                'Video {} cold not be uploaded to YouTube due to API quota limits'
                .format(package.get_data()['additional_info']['output_video']))
            self.__logger.separator()
        else:
            self.__logger.log('Video {} uploaded to YouTube'.format(
                package.get_data()['additional_info']['output_video']))
            self.__logger.separator()
            output = output.decode('utf-8')

            list_output = output.split(' ')
            for i in range(len(list_output)):
                if list_output[i] == 'ID:':
                    index = i + 1

            video_id = list_output[index].replace('\nThumbnail', '')

            url = 'https://www.youtube.com/watch?v=' + video_id
            package.get_data()['additional_info']['url_video'] = url
            package.update()
        return True