Example #1
0
def download_from_youtube(url: str) -> str:
    with wrap_context('downloading from youtube', url=url):
        log.info('downloading from youtube...', url=url)

        uid = str(uuid.uuid4())
        filename = f'trimmer_dl_{uid}'

        ydl_opts = {
            'format': 'bestaudio/best',
            'postprocessors': [{
                'key': 'FFmpegExtractAudio',
                'preferredcodec': 'mp3',
                'preferredquality': '192',
            }],
            'outtmpl': f'{filename}.%(ext)s'
        }
        with youtube_dl.YoutubeDL(ydl_opts) as ydl:
            retcode = ydl.download([url])
            assert retcode == 0

        full_filename = f'{filename}.mp3'
        assert os.path.isfile(full_filename)
        log.info('song downloaded', tmpfile=full_filename)

        return full_filename
Example #2
0
    def respond_to_client(self, response: HttpResponse):
        with wrap_context('responding to client'):
            self.send_response_only(response.status_code)

            if has_header(response.headers, 'Content-Encoding'):
                del response.headers['Content-Encoding']
                if self.config.verbose >= 2:
                    log.debug('removing Content-Encoding header')

            if not has_header(response.headers, 'Content-Length') and \
                    not has_header(response.headers, 'Transfer-Encoding') and response.content:
                response.headers['Content-Length'] = str(len(response.content))
                log.warn('adding missing Content-Length header')

            if has_header(response.headers, 'Content-Length') and has_header(
                    response.headers, 'Transfer-Encoding'):
                del response.headers['Content-Length']
                log.warn(
                    'removed Content-Length header conflicting with Transfer-Encoding'
                )

            for name, value in response.headers.items():
                self.send_header(name, value)
            self.end_headers()

            if self.config.allow_chunking and response.headers.get(
                    'Transfer-Encoding') == 'chunked':
                send_chunked_response(self.wfile, response.content)
            else:
                self.wfile.write(response.content)
            self.close_connection = True
            if self.config.verbose >= 2:
                log.debug('> response sent',
                          client_addr=self.client_address[0],
                          client_port=self.client_address[1])
Example #3
0
    def generate_response(self, request_0: HttpRequest) -> HttpResponse:
        with wrap_context('generating response'):
            request = request_0.transform(self.extensions.transform_request)
            if request != request_0 and self.config.verbose >= 2:
                log.debug('request transformed')

            immediate_reponse = self.find_immediate_response(request)
            if immediate_reponse:
                return immediate_reponse.log('> immediate response',
                                             self.config.verbose)

            self.cache.clear_old()
            if self.cache.has_cached_response(request):
                return self.cache.replay_response(request).log(
                    '> Cache: returning cached response', self.config.verbose)

            if self.config.replay and self.config.verbose:
                log.warn('request not found in cache', path=request.path)
            response: HttpResponse = proxy_request(
                request,
                default_url=self.config.dst_url,
                timeout=self.config.timeout,
                verbose=self.config.verbose)

            if self.cache.saving_enabled(request, response):
                self.cache.save_response(request, response)

            return response
Example #4
0
def proxy_request(request: HttpRequest, default_url: str, timeout: int,
                  verbose: int) -> HttpResponse:
    dst_url = request.dst_url if request.dst_url else default_url
    with logerr():
        with wrap_context('proxying to URL',
                          dst_url=dst_url,
                          path=request.path,
                          content=request.content):
            url = f'{dst_url}{request.path}'
            if verbose:
                log.debug(f'>> proxying to', url=url)
            response = requests.request(request.method,
                                        url,
                                        verify=False,
                                        allow_redirects=False,
                                        stream=False,
                                        timeout=timeout,
                                        headers=request.headers,
                                        data=request.content)
            content: bytes = response.content
            http_response = HttpResponse(status_code=response.status_code,
                                         headers=dict(response.headers),
                                         content=content)
            return http_response.log('<< received', verbose)

    # Bad Gateway response
    error_msg = f'Proxying failed: {dst_url}'
    return HttpResponse(status_code=502,
                        headers={
                            'X-Man-Error': 'proxying failed',
                        }).set_content(error_msg)
Example #5
0
def read_mp3_artist_title(mp3_file: str) -> Tuple[str, str]:
    with wrap_context('extracting mp3 metadata', mp3_file=mp3_file):
        tag_artist, tag_title = read_mp3_tags(mp3_file)
        file_artist, file_title = extract_filename_artist_title(mp3_file)
        artist = tag_artist or file_artist
        title = tag_title or file_title
        log.info('metadata inferred', artist=artist, title=title)
        return artist, title
Example #6
0
def tag_mp3(mp3_file: str, artist: str, title: str):
    with wrap_context('tagging mp3', artist=artist, title=title, mp3_file=mp3_file):
        log.info('tagging mp3...', artist=artist, title=title)

        audiofile = eyed3.load(mp3_file)
        audiofile.tag.artist = artist
        audiofile.tag.title = title

        audiofile.tag.save(version=ID3_V1_1)
        audiofile.tag.save(version=ID3_V2_4)
Example #7
0
def read_mp3_tags(mp3_file: str) -> Tuple[str, str]:
    with wrap_context('reading mp3 tags'):
        audiofile = eyed3.load(mp3_file)
        if audiofile is None or audiofile.tag is None:
            log.warn('no IDv3 tags read', mp3_file=mp3_file)
            return '', ''

        artist = _oremptystr(audiofile.tag.artist).strip()
        title = _oremptystr(audiofile.tag.title).strip()
        log.info('IDv3 tags read', mp3_file=mp3_file, artist=artist, title=title)
        return artist, title
Example #8
0
def normalize_song(mp3_file: str,
                   no_trim: bool,
                   no_fade: bool,
                   no_normalize: bool,
                   user_trim_start: Optional[float] = None,
                   user_trim_end: Optional[float] = None,
                   user_gain: Optional[float] = None):
    with wrap_context('normalizing mp3', mp3_file=mp3_file):
        log.info('loading song...', mp3_file=mp3_file)
        song = AudioSegment.from_mp3(mp3_file)

        if not no_normalize:
            if user_gain is not None:
                gain = user_gain
            else:
                volume = calculate_volume(song)
                log.info('normalizing volume level...',
                         volume=f'{volume:.2f}dB',
                         dBFS=f'{song.dBFS:.2f}dB')
                gain = -volume
            song = song.apply_gain(gain)
            log.info('volume normalized', gain=f'{gain:.2f}dB')

        if not no_trim:
            log.info('trimming silence...')
            start_trim = user_trim_start * 1000 if user_trim_start is not None else detect_leading_silence(
                song)
            end_trim = user_trim_end * 1000 if user_trim_end is not None else detect_leading_silence(
                song.reverse(), margin=0)
            pre_duration = len(song)
            song = song[start_trim:len(song) - end_trim]
            post_duration = len(song)
            log.info('silence trimmed',
                     trim_start=duration_to_human(start_trim),
                     trim_end=duration_to_human(end_trim),
                     duration_before=duration_to_human(pre_duration),
                     duration_after=duration_to_human(post_duration))

        if not no_fade:
            fade_in_duration = 100
            fade_out_duration = 1000
            log.info('applying fade-in & fade-out...',
                     fade_in=duration_to_human(fade_in_duration),
                     fade_out=duration_to_human(fade_out_duration))
            song = song.fade_in(fade_in_duration).fade_out(fade_out_duration)

        duartion = len(song)
        log.info('saving song...',
                 mp3_file=mp3_file,
                 duration=duration_to_human(duartion))
        song.export(mp3_file, format="mp3")
Example #9
0
def rename_song(mp3_file: str, artist: str, title: str) -> str:
    with wrap_context('renaming song',
                      artist=artist,
                      title=title,
                      mp3_file=mp3_file):
        dirname, filename = os.path.split(mp3_file)
        if artist.strip():
            new_filename = f'{artist.strip()} - {title.strip()}.mp3'
        else:
            new_filename = f'{title.strip()}.mp3'
        new_path = Path(dirname) / new_filename
        os.rename(mp3_file, new_path)
        log.info('song renamed', new_name=new_path)
        return new_path
Example #10
0
 def incoming_request(self) -> HttpRequest:
     with wrap_context('building incoming request'):
         headers_dict = {
             key: self.headers[key]
             for key in self.headers.keys()
         }
         method = self.command.upper()
         content_len = int(get_header(headers_dict, 'Content-Length', '0'))
         content: bytes = self.rfile.read(
             content_len) if content_len else b''
         return HttpRequest(requestline=self.requestline,
                            method=method,
                            path=self.path,
                            headers=headers_dict,
                            content=content,
                            client_addr=self.client_address[0],
                            client_port=self.client_address[1],
                            timestamp=now_seconds())
Example #11
0
def trim_url(url: str, user_artist: Optional[str], user_title: Optional[str],
             no_trim: bool, no_fade: bool, no_normalize: bool,
             trim_start: Optional[float], trim_end: Optional[float], gain: Optional[float], output: Optional[str]):
    with wrap_context('url song'):
        yt_artist, yt_title = extract_youtube_artist_title(url)
        log.info('artist & title extracted from youtube page', artist=yt_artist, title=yt_title)
        artist = user_artist or enter_or_default('Artist', default=yt_artist)
        title = user_title or enter_or_default('Title', default=yt_title)
        log.info('song name set', name=f'{artist} - {title}')

        mp3_file = download_from_youtube(url)
        if output:
            mp3_file = rename_output_song(mp3_file, output)
        else:
            mp3_file = rename_song(mp3_file, artist, title)
        normalize_song(mp3_file, no_trim, no_fade, no_normalize, trim_start, trim_end, gain)
        tag_mp3(mp3_file, artist, title)

        log.info('song saved', mp3_file=mp3_file)
Example #12
0
def trim_mp3(mp3_file: str, user_artist: Optional[str], user_title: Optional[str],
             no_trim: bool, no_fade: bool, no_normalize: bool, no_rename: bool,
             trim_start: Optional[float], trim_end: Optional[float], gain: Optional[float], output: Optional[str]):
    with wrap_context('mp3 song'):
        assert os.path.isfile(mp3_file), 'input file should exist'

        if output:
            shutil.copyfile(mp3_file, output)
            mp3_file = output

        tag_artist, tag_title = read_mp3_artist_title(mp3_file)
        artist = user_artist or tag_artist or enter_or_default('Artist', default='')
        title = user_title or tag_title or enter_or_default('Title', default='')

        if not output and not no_rename:
            mp3_file = rename_song(mp3_file, artist, title)
        normalize_song(mp3_file, no_trim, no_fade, no_normalize, trim_start, trim_end, gain)
        tag_mp3(mp3_file, artist, title)

        log.info('song saved', mp3_file=mp3_file)
Example #13
0
def setup_proxy(listen_port: int, listen_ssl: bool, dst_url: str, record: bool,
                record_file: str, replay: bool, replay_throttle: bool,
                replay_clear_cache: bool, replay_clear_cache_seconds: int,
                config: str, verbose: int):
    with logerr():
        with wrap_context('initialization'):
            extensions = load_extensions(config)
            _config = Config(
                listen_port=listen_port,
                listen_ssl=listen_ssl,
                dst_url=dst_url,
                record=record,
                record_file=record_file,
                replay=replay,
                replay_throttle=replay_throttle,
                replay_clear_cache=replay_clear_cache,
                replay_clear_cache_seconds=replay_clear_cache_seconds,
                verbose=verbose,
            )
            if extensions.override_config:
                extensions.override_config(_config)
            log.info('Configuration set', **asdict(_config))

            RequestHandler.extensions = extensions
            RequestHandler.config = _config
            RequestHandler.cache = RequestCache(extensions, _config)

            TCPServer.allow_reuse_address = True
            httpd = TCPServer((_config.listen_addr, _config.listen_port),
                              RequestHandler)
            if _config.listen_ssl:
                httpd.socket = ssl.wrap_socket(httpd.socket,
                                               certfile='./dev-cert.pem',
                                               server_side=True)
            log.info(
                f'Listening on {_config.listen_scheme} port {_config.listen_port}...'
            )
            try:
                httpd.serve_forever()
            finally:
                httpd.server_close()
Example #14
0
def fetch_youtube_metadata(url: str) -> Tuple[str, str, str]:
    with wrap_context('fetching title from youtube', url=url):
        log.info('fetching metadata from youtube page...', url=url)

        ydl_opts = {
            'format': 'bestaudio/best',
            'postprocessors': [{
                'key': 'FFmpegExtractAudio',
                'preferredcodec': 'mp3',
                'preferredquality': '192',
            }],
            'outtmpl': '%(title)s.%(ext)s',
        }
        with youtube_dl.YoutubeDL(ydl_opts) as ydl:
            einfo = ydl.extract_info(url, download=False)

            track = einfo.get('track')
            artist = einfo.get('artist') or einfo.get('creator')
            full_title = einfo.get('title') or einfo.get('alt_title')

            log.info('youtube page metadata fetched', yt_title=full_title, artist=artist, track=track)
            return artist, track, full_title
Example #15
0
def rename_output_song(mp3_file: str, output: str) -> str:
    with wrap_context('renaming song', mp3_file=mp3_file, output=output):
        os.rename(mp3_file, output)
        log.info('song renamed', new_name=output)
        return output
Example #16
0
def extract_filename_artist_title(mp3_file: str) -> Tuple[str, str]:
    with wrap_context('extracting metadata from filename'):
        _, filename = os.path.split(mp3_file)
        if filename.lower().endswith('.mp3'):
            filename = mp3_file[:-4]
        return extract_artist_title(filename)