예제 #1
0
    def with_ffmpeg(self):
        ffmpeg_pre = 'ffmpeg -y '

        if not log.level == 10:
            ffmpeg_pre += '-hide_banner -nostats -v panic '

        input_ext = self.input_song.split('.')[-1]
        output_ext = self.output_song.split('.')[-1]

        if input_ext == 'm4a':
            if output_ext == 'mp3':
                ffmpeg_params = '-codec:v copy -codec:a libmp3lame -q:a 2 '
            elif output_ext == 'webm':
                ffmpeg_params = '-c:a libopus -vbr on -b:a 192k -vn '

        elif input_ext == 'webm':
            if output_ext == 'mp3':
                ffmpeg_params = ' -ab 192k -ar 44100 -vn '
            elif output_ext == 'm4a':
                ffmpeg_params = '-cutoff 20000 -c:a libfdk_aac -b:a 192k -vn '

        command = '{0}-i {1} {2}{3}'.format(
            ffmpeg_pre, os.path.join(self.folder,
                                     self.input_song), ffmpeg_params,
            os.path.join(self.folder, self.output_song)).split(' ')

        log.debug(command)
        return subprocess.call(command)
예제 #2
0
    def with_ffmpeg(self):
        ffmpeg_pre = 'ffmpeg -y '

        if not log.level == 10:
            ffmpeg_pre += '-hide_banner -nostats -v panic '

        _, input_ext = os.path.splitext(self.input_file)
        _, output_ext = os.path.splitext(self.output_file)

        if input_ext == '.m4a':
            if output_ext == '.mp3':
                ffmpeg_params = '-codec:v copy -codec:a libmp3lame -q:a 2 '
            elif output_ext == '.webm':
                ffmpeg_params = '-c:a libopus -vbr on -b:a 192k -vn '

        elif input_ext == '.webm':
            if output_ext == '.mp3':
                ffmpeg_params = ' -ab 192k -ar 44100 -vn '
            elif output_ext == '.m4a':
                ffmpeg_params = '-cutoff 20000 -c:a libfdk_aac -b:a 192k -vn '

        ffmpeg_pre += ' -i'
        command = ffmpeg_pre.split() + [
            self.input_file
        ] + ffmpeg_params.split() + [self.output_file]

        log.debug(command)
        return subprocess.call(command)
예제 #3
0
def write_tracks(tracks, text_file):
    log.info(u'Writing {0} tracks to {1}'.format(tracks['total'], text_file))
    track_urls = []
    with open(text_file, 'a') as file_out:
        while True:
            for item in tracks['items']:
                if 'track' in item:
                    track = item['track']
                else:
                    track = item
                try:
                    track_url = track['external_urls']['spotify']
                    log.debug(track_url)
                    file_out.write(track_url + '\n')
                    track_urls.append(track_url)
                except KeyError:
                    log.warning(
                        u'Skipping track {0} by {1} (local only?)'.format(
                            track['name'], track['artists'][0]['name']))
            # 1 page = 50 results
            # check if there are more pages
            if tracks['next']:
                tracks = spotify.next(tracks)
            else:
                break
    return track_urls
예제 #4
0
    def with_ffmpeg(self):
        ffmpeg_pre = 'ffmpeg -y '

        if not log.level == 10:
            ffmpeg_pre += '-hide_banner -nostats -v panic '

        _, input_ext = os.path.splitext(self.input_file)
        _, output_ext = os.path.splitext(self.output_file)

        ffmpeg_params = ''

        if input_ext == '.m4a':
            if output_ext == '.mp3':
                ffmpeg_params = '-codec:v copy -codec:a libmp3lame -ar 44100 '
            elif output_ext == '.webm':
                ffmpeg_params = '-codec:a libopus -vbr on '

        elif input_ext == '.webm':
            if output_ext == '.mp3':
                ffmpeg_params = '-codec:a libmp3lame -ar 44100 '
            elif output_ext == '.m4a':
                ffmpeg_params = '-cutoff 20000 -codec:a libfdk_aac -ar 44100 '

        if output_ext == '.flac':
            ffmpeg_params = '-codec:a flac -ar 44100 '

        # add common params for any of the above combination
        ffmpeg_params += '-b:a 192k -vn '
        ffmpeg_pre += ' -i'
        command = ffmpeg_pre.split() + [
            self.input_file
        ] + ffmpeg_params.split() + [self.output_file]

        log.debug(command)
        return subprocess.call(command)
예제 #5
0
def _download_songs(folder, songs, progress_func):
    log.info(u'Preparing to download {} songs'.format(len(songs)))
    downloaded_songs = []

    for number, raw_song in enumerate(songs):
        try:
            progress_func({"current": number, "total": len(songs)})

            _download_single(folder, raw_song, number=number)
        # token expires after 1 hour
        except spotipy.client.SpotifyException:
            # refresh token when it expires
            log.debug('Token expired, generating new one and authorizing')
            spotify_tools.init()
            _download_single(folder, raw_song, number=number)
        # detect network problems
        except (urllib.request.URLError, TypeError, IOError):
            songs.append(raw_song)

            log.warning(
                'Failed to download song. Will retry after other songs\n',
                exc_info=True)

            # wait 0.5 sec to avoid infinite looping
            time.sleep(0.5)
            continue

        downloaded_songs.append(raw_song)

    return downloaded_songs
예제 #6
0
def write_playlist(playlist_url, path):
    playlist = fetch_playlist(playlist_url)
    to_download = os.getenv('PLAYLISTS', '')
    log.debug(f'downloading playlists: {to_download}')
    to_download = slugify(to_download, ok=',-_()[]{}').split(sep=',')
    playlist_name = slugify(playlist['name'], ok='-_()[]{}')
    if playlist_name in to_download:
        tracks = playlist['tracks']
        text_file = u'{0}/{1}.txt'.format(path, playlist_name)
        return write_tracks(tracks, text_file)
예제 #7
0
    def with_avconv(self):
        if log.level == 10:
            level = 'debug'
        else:
            level = '0'

        command = ['avconv', '-loglevel', level, '-i',
                   self.input_file, '-ab', '192k',
                   self.output_file]

        log.debug(command)
        return subprocess.call(command)
예제 #8
0
    def with_avconv(self):
        if log.level == 10:
            level = 'debug'
        else:
            level = '0'

        command = [
            'avconv', '-loglevel', level, '-i',
            os.path.join(self.folder, self.input_song), '-ab', '192k',
            os.path.join(self.folder, self.output_song)
        ]

        log.debug(command)
        return subprocess.call(command)
예제 #9
0
    def with_avconv(self):
        if log.level == 10:
            level = 'debug'
        else:
            level = '0'

        command = [
            'avconv', '-loglevel', level, '-i', self.input_file, '-ab', '192k',
            self.output_file, '-y'
        ]

        if self.trim_silence:
            log.warning('--trim-silence not supported with avconv')

        log.debug(command)
        return subprocess.call(command)
예제 #10
0
def generate_metadata(raw_song):
    """ Fetch a song's metadata from Spotify. """
    if internals.is_spotify(raw_song):
        # fetch track information directly if it is spotify link
        log.debug('Fetching metadata for given track URL')
        meta_tags = _getClient().track(raw_song)
    else:
        # otherwise search on spotify and fetch information from first result
        log.debug('Searching for "{}" on Spotify'.format(raw_song))
        try:
            search_result = _getClient().search(raw_song, limit=1)
            meta_tags = search_result['tracks']['items'][0]
        except IndexError:
            return None
    artist = _getClient().artist(meta_tags['artists'][0]['id'])
    album = _getClient().album(meta_tags['album']['id'])

    try:
        meta_tags[u'genre'] = titlecase(artist['genres'][0])
    except IndexError:
        meta_tags[u'genre'] = None
    try:
        meta_tags[u'copyright'] = album['copyrights'][0]['text']
    except IndexError:
        meta_tags[u'copyright'] = None
    try:
        meta_tags[u'external_ids'][u'isrc']
    except KeyError:
        meta_tags[u'external_ids'][u'isrc'] = None

    meta_tags[u'release_date'] = album['release_date']
    meta_tags[u'publisher'] = album['label']
    meta_tags[u'total_tracks'] = album['tracks']['total']

    log.debug('Fetching lyrics')

    try:
        meta_tags['lyrics'] = lyricwikia.get_lyrics(
            meta_tags['artists'][0]['name'], meta_tags['name'])
    except lyricwikia.LyricsNotFound:
        meta_tags['lyrics'] = None

    # Some sugar
    meta_tags['year'], *_ = meta_tags['release_date'].split('-')
    meta_tags['duration'] = meta_tags['duration_ms'] / 1000.0
    # Remove unwanted parameters
    del meta_tags['duration_ms']
    del meta_tags['available_markets']
    del meta_tags['album']['available_markets']

    log.debug(pprint.pformat(meta_tags))
    return meta_tags
예제 #11
0
def get_playlists(username):
    """ Fetch user playlists when using the -u option. """
    playlists = spotify.user_playlists(username)
    links = []
    check = 1

    while True:
        for playlist in playlists['items']:
            # in rare cases, playlists may not be found, so playlists['next']
            # is None. Skip these. Also see Issue #91.
            if playlist['name'] is not None:
                log.info(u'{0:>5}. {1:<30}  ({2} tracks)'.format(
                    check, playlist['name'], playlist['tracks']['total']))
                playlist_url = playlist['external_urls']['spotify']
                log.debug(playlist_url)
                links.append(playlist_url)
                check += 1
        if playlists['next']:
            playlists = spotify.next(playlists)
        else:
            break

    return links
예제 #12
0
def _check_exists(folder, music_file, raw_song, meta_tags):
    """ Check if the input song already exists in the given folder. """
    log.debug('Cleaning any temp files and checking '
              'if "{}" already exists'.format(music_file))
    if not os.path.isdir(folder):
        os.mkdir(folder)

    songs = os.listdir(folder)
    for song in songs:
        if song.endswith('.temp'):
            os.remove(os.path.join(folder, song))
            continue
        # check if a song with the same name is
        # already present in the given folder
        if os.path.splitext(song)[0] == music_file:
            log.debug('Found an already existing song: "{}"'.format(song))
            if internals.is_spotify(raw_song):
                # check if the already downloaded song has correct metadata
                # if not, remove it and download again without prompt
                already_tagged = metadata.compare(os.path.join(folder, song),
                                                  meta_tags)
                log.debug(
                    'Checking if it is already tagged correctly? {}'.format(
                        already_tagged))
                if not already_tagged:
                    os.remove(os.path.join(folder, song))
                    return False

            log.warning('"{}" already exists'.format(song))
            if const.config.overwrite == 'force':
                os.remove(os.path.join(folder, song))
                log.info('Overwriting "{}"'.format(song))
                return False
            elif const.config.overwrite == 'skip':
                log.info('Skipping "{}"'.format(song))
                return True
    return False
예제 #13
0
def play_and_record(track_uri="spotify:track:1L1dpImK36DoZPr7rxy0hJ", filename="foo", songname="bar"):
    log.debug("entered playing and record")
    client.playback.stop()
    client.tracklist.set_repeat(False)
    client.tracklist.clear()
    client.tracklist.set_single(True)
    client.mixer.set_mute(False)
    client.mixer.set_volume(100)
    client.tracklist.add(uris=[track_uri])
    listener.filename = filename
    listener.songname = songname

    try:
        # Activate cache
        if caching_enabled:
            log.debug('caching start')
            client.playback.play()
            timeout = time.time() + 20
            while listener.get_playback_stopped():
                if time.time() > timeout:
                    raise RuntimeWarning('Could not play track for caching {}'.format(songname))
                time.sleep(0.05)
            time.sleep(3)
            log.debug('stopping cache play')
            client.playback.stop()
            timeout = time.time() + 20
            while not listener.get_playback_stopped():
                if time.time() > timeout:
                    raise RuntimeWarning('Caching record took too long {}'.format(songname))
                time.sleep(0.05)
            log.debug('caching end')

        # start recording
        listener.reset_play_time()
        listener.start_recording()
        # make sure thread has started
        timeout = time.time() + 15
        while not listener.record_thread.is_alive():
            if time.time() > timeout:
                raise RuntimeError('Record thread still not alive track {}'.format(songname))

        time.sleep(3)
        client.playback.play()
        timeout = time.time() + 10
        while listener.get_playback_stopped():
            if time.time() > timeout:
                raise RuntimeWarning('Could not play track {}'.format(songname))
            time.sleep(0.05)

        # wait for end
        timeout = time.time() + 60 * 20
        while not listener.get_playback_stopped():
            if time.time() > timeout:
                raise RuntimeWarning('Record took longer than 20 minutes track {}'.format(songname))
            time.sleep(0.1)

        # make sure thread has stopped
        timeout = time.time() + 15
        while listener.record_thread.is_alive():
            if time.time() > timeout:
                raise RuntimeError('Record thread still alive track {}'.format(songname))

    except RuntimeWarning as e:
        log.error(str(e))
    finally:
        client.playback.stop()
        return listener.play_time
예제 #14
0
 def mopidy_playback_started(self):
     self.play_time = time.time()
     self.mutex.acquire_write()
     self.playback_stopped = False
     self.mutex.release_write()
     log.debug('started playback')
예제 #15
0
def _download_songs(folder, songs, progress_func, max_retries=3):
    log.info(u'Preparing to download {} songs'.format(len(songs)))
    downloaded_songs = []

    failed = 0
    pending = len(songs)
    retrying = 0
    total = len(songs)
    failed_songs = {}

    songs_enumeration = list(enumerate(songs))

    for number, raw_song in songs_enumeration:
        try:
            progress_func({
                "retrying": retrying,
                "failed": failed,
                "pending": pending,
                "total": total,
            })

            _download_single(folder, raw_song, number=number)

            downloaded_songs.append(raw_song)

            pending = pending - 1

            if failed_songs.get(str(number), 0) > 0:
                retrying = retrying - 1
        # token expires after 1 hour
        except spotipy.client.SpotifyException:
            # refresh token when it expires
            log.debug('Token expired, generating new one and authorizing')
            spotify_tools.init()
            _download_single(folder, raw_song, number=number)
        # Retry if possible
        except Exception:
            if failed_songs.get(str(number), 0) == max_retries:
                log.exception('Error downloading song {}'.format(raw_song))

                failed = failed + 1
                retrying = retrying - 1
                pending = pending - 1
            else:
                retry = failed_songs.get(str(number), 0)

                if retry == 0:
                    retrying = retrying + 1

                log.warning(
                    'Failed to download song. Will retry after other songs\n',
                    exc_info=True)

                failed_songs[str(number)] = retry + 1
                songs_enumeration.append((number, raw_song))

                time.sleep(0.5)

    progress_func({
        "retrying": retrying,
        "failed": failed,
        "pending": pending,
        "total": total,
    })

    return downloaded_songs
예제 #16
0
def _download_single(folder, raw_song, number=None):
    """ Logic behind downloading a song. """
    if internals.is_youtube(raw_song):
        log.debug('Input song is a YouTube URL')
        content = youtube_tools.go_pafy(raw_song, meta_tags=None)
        raw_song = slugify(content.title).replace('-', ' ')
        meta_tags = spotify_tools.generate_metadata(raw_song)
        meta_tags['number'] = number
    else:
        meta_tags = spotify_tools.generate_metadata(raw_song)
        meta_tags['number'] = number
        content = youtube_tools.go_pafy(raw_song, meta_tags)

    if content is None:
        log.debug('Found no matching video')
        return

    if const.config.download_only_metadata and meta_tags is None:
        log.info('Found no metadata. Skipping the download')
        return

    # "[number]. [artist] - [song]" if downloading from list
    # otherwise "[artist] - [song]"
    youtube_title = youtube_tools.get_youtube_title(content, number)
    log.info('{} ({})'.format(youtube_title, content.watchv_url))

    # generate file name of the song to download
    songname = content.title

    if meta_tags is not None:
        refined_songname = internals.format_string(const.config.file_format,
                                                   meta_tags,
                                                   slugification=True)
        log.debug('Refining songname from "{0}" to "{1}"'.format(
            songname, refined_songname))
        if not refined_songname == ' - ':
            songname = refined_songname
    else:
        log.warning('Could not find metadata')
        songname = internals.sanitize(songname)

    if not _check_exists(folder, songname, raw_song, meta_tags):
        # deal with file formats containing slashes to non-existent directories
        songpath = os.path.join(folder, os.path.dirname(songname))
        os.makedirs(songpath, exist_ok=True)
        input_song = songname + const.config.input_ext
        output_song = songname + const.config.output_ext
        if youtube_tools.download_song(songpath, input_song, content):
            try:
                convert.song(input_song,
                             output_song,
                             folder,
                             avconv=const.config.avconv,
                             trim_silence=const.config.trim_silence)
            except FileNotFoundError:
                encoder = 'avconv' if const.config.avconv else 'ffmpeg'
                log.warning(
                    'Could not find {0}, skipping conversion'.format(encoder))
                const.config.output_ext = const.config.input_ext
                output_song = songname + const.config.output_ext

            if not const.config.input_ext == const.config.output_ext:
                os.remove(os.path.join(folder, input_song))
            if not const.config.no_metadata and meta_tags is not None:
                metadata.embed(os.path.join(folder, output_song), meta_tags)
            return True
        else:
            log.exception('Error downloading song {}'.format(raw_song))
예제 #17
0
 def start_recording(self):
     log.debug('start recording')
     self.record_thread = threading.Thread(
         target=record,
         args=[self.filename, self.songname, self.get_playback_stopped])
     self.record_thread.start()
예제 #18
0
 def reset_play_time(self):
     log.debug('resetting play time')
     self.play_time = 0