目次

python-vlc - VLC の Python バインディング

リアル世界でのVLC
ごめんなさい🙏ブラ男氏が縄張りを主張しちゃいました🐶😅💦

本家: Python bindings for VLC
ドキュメント: API Documentation

インストール

$ python3 -m venv py3_vlc
$ . py3_vlc/bin/activate
(py3_vlc) $ pip install python-vlc
Collecting python-vlc
  Downloading python_vlc-3.0.7110-py3-none-any.whl (80 kB)
     |████████████████████████████████| 80 kB 721 kB/s
Installing collected packages: python-vlc
Successfully installed python-vlc-3.0.7110

Python 製コマンドライン VLC プレイヤー

メディアリストをループ再生するサンプルコードである。
VLC インスタンスは Raspberry Pi をヘッドレスで運用すること前提に '--no-video' オプションを指定しているので、必要に応じてインスタンスの起動オプションを修正してください。

pyvlcplayer.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
 
import vlc
import os
import pathlib
import readline
 
# 拡張子は小文字で定義 !!
exts = ('.mp3', '.m4a', '.wav', '.wma', '.mpg', '.mov', '.mkv', '.mp4', '.webm')
#exts = ('.mp3', '.m4a', '.flac', '.wav')
 
HOME = os.path.expanduser('~')
READLINE_HISTORY_FILE = '.pyvlcplayer_history'
READLINE_HISTORY_PATH = os.path.join(HOME, READLINE_HISTORY_FILE)
EQ_PRESET_BASS_BOOST = [14.5, 15.5, 10.5, 9.3, 10.8, 15.3, 0.0, 2.2, 7.8, 14.6]
 
PATH = '/var/samba/MusicData/TRF'
 
def walk(dir, exts):
    result = []
    for path, dirs, files in os.walk(os.path.join(dir, '.'), followlinks=True):
        for file in files:
            #print(file)
            if file.lower().endswith(exts):
                dir_path = os.path.abspath(path)
                result.append(os.path.join(dir_path, file))
 
    return result
 
def printVLCMeta(media, path):
    title = media.get_meta(vlc.Meta.Title)
    artist = media.get_meta(vlc.Meta.Artist)
    album = media.get_meta(vlc.Meta.Album)
    trackNumber = media.get_meta(vlc.Meta.TrackNumber)
    trackNumber = trackNumber if not trackNumber is None else 0
    trackTotal = media.get_meta(vlc.Meta.TrackTotal)
    trackTotal = trackTotal if not trackTotal is None else 0
    genre = media.get_meta(vlc.Meta.Genre)
    #url = media.get_meta(vlc.Meta.URL)
    fileSize = os.path.getsize(path)
 
    print(f'Album: {album}')
    print(f'Title: {title}')
    print(f'File Size: {fileSize:,} bytes')
    print(f'Artist: {artist}')
    print(f'Genre: {genre} Track: {trackNumber: >3}/{trackTotal}')
    print(f'Path: {path}')
    #print(f'URL: {url}')
 
def main():
    pathlib.Path(READLINE_HISTORY_PATH).touch()
    readline.read_history_file(READLINE_HISTORY_PATH)
 
    flagEQ = False
 
    medias = walk(PATH, exts)
    medias.sort()
    print(f'Media Count: {len(medias)}')
 
    # No Video mode for headless Raspberry Pi
    vlcInstance = vlc.Instance('--no-video')
 
    #mediaList = vlc.MediaList()
    mediaList = vlcInstance.media_list_new()
 
    for index, media in enumerate(medias):
        print(f'Index: {index: >4} Path: {media}')
        mediaList.add_media(os.path.join(media[0], media[1]))
 
    #mediaListPlayer = vlc.MediaListPlayer()
    mediaListPlayer = vlcInstance.media_list_player_new()
 
    mediaListPlayer.set_media_list(mediaList)
    mediaListPlayer.set_playback_mode(vlc.PlaybackMode.loop)
    mediaListPlayer.play()
 
    mediaPlayer = mediaListPlayer.get_media_player()
 
    print('🎊🎂🍩🍩🍩 libvlc information 🍩🍩🍩🎂🎊')
    print(f"libvlc version: '{vlc.libvlc_get_version().decode()}' ✨")
    print(f"libvlc compiler version: '{vlc.libvlc_get_compiler().decode()}' ✨")
    print(f"libvlc changeset: '{vlc.libvlc_get_changeset().decode()}' ✨\n")
 
    while True:
        print('po: POSITION, i: INDEX/Info, p: PREVIOUS, n: NEXT, q: QUIT')
        print('eq [frequency band value list]: Equalizer On/Off')
        # 入力待ちプロンプト
        data = input('>> ')
 
        #POSITION
        if data == 'po':
            pos = mediaPlayer.get_position()
            print(f'Position: {pos}')
 
        #INDEX/Info
        elif data == 'i':
            media = mediaPlayer.get_media()
            index = mediaList.index_of_item(media)
            path = os.path.join(medias[index][0], medias[index][1])
            print(f'Index: {index}')
            printVLCMeta(media, path)
 
        #PREVIOUS
        elif data == 'p':
            mediaListPlayer.previous()
            media = mediaPlayer.get_media()
            index = mediaList.index_of_item(media)
            fileName = media.get_meta(0)
            print(f'Previous -> Current Index: {index} File: {fileName}')
 
        #NEXT
        elif data == 'n':
            mediaListPlayer.next()
            media = mediaPlayer.get_media()
            index = mediaList.index_of_item(media)
            fileName = media.get_meta(0)
            print(f'Next -> Current Index: {index} File: {fileName}')
 
        #Set Equalizer
        elif data.startswith('eq'):
            #EQ Set
            if len(data) > 3 or not flagEQ:
                flagEQ = True
                eqFreqs = []
 
                eqAmps = list(map(float, data[3:].split()))
                if len(eqAmps) == 0:
                    eqAmps = EQ_PRESET_BASS_BOOST
 
                bandCount = vlc.libvlc_audio_equalizer_get_band_count()
                print(f'Band Count: {bandCount}')
 
                eq = vlc.AudioEqualizer()
 
                for bandIndex in range(bandCount):
                    try:
                       amp = eqAmps[bandIndex]
                    except IndexError:
                       amp = 0
                    eq.set_amp_at_index(amp, bandIndex)
                    eqFreqs.append(vlc.libvlc_audio_equalizer_get_band_frequency(bandIndex))
 
                print(f'Freq: {" ".join(map(str, eqFreqs))}')
                print(f'Amp: {" ".join(map(str, eqAmps))}')
            #EQ Off
            elif flagEQ:
                flagEQ = False
                eq = None
 
            print(f'Equalizer: {"On" if flagEQ else "Off"}')
            mediaPlayer.set_equalizer(eq)
 
        #QUIT
        elif data == 'q':
            mediaListPlayer.stop()
            readline.write_history_file(READLINE_HISTORY_PATH)
            print('Quit.')
            break
 
if __name__ == '__main__':
    main()

使い方

os.walk() に followlinks=True オプションを追加。(シンボルリンクを辿るように指定)
eq で VLC のイコライザーを設定。

eq <- イコライザー Off の場合は EQ_PRESET_BASS_BOOST で On にする。
Band Count: 10
Freq: 31.25 62.5 125.0 250.0 500.0 1000.0 2000.0 4000.0 8000.0 16000.0
Amp: 20.0 10.5 2.8 9.3 14.8 6.3 0.0 2.2 7.8 15.6
Equalizer: On
po: POSITION, i: INDEX/Info, p: PREVIOUS, n: NEXT, q: QUIT
eq [frequency band value list]: Equalizer On/Off
eq 20 20 20 <- イコライザー 30Hz 60Hz 125Hz を 20.0 dB に設定する。
Band Count: 10
Freq: 31.25 62.5 125.0 250.0 500.0 1000.0 2000.0 4000.0 8000.0 16000.0
Amp: 20.0 20.0 20.0
Equalizer: On
po: POSITION, i: INDEX/Info, p: PREVIOUS, n: NEXT, q: QUIT
eq [frequency band value list]: Equalizer On/Off
eq <- イコライザー On の場合は Off にする。
Equalizer: Off
po: POSITION, i: INDEX/Info, p: PREVIOUS, n: NEXT, q: QUIT
eq [frequency band value list]: Equalizer On/Off

Raspberry Pi をヘッドレスで運用している場合は以下のエラーで動作が不安定になる場合がある。

>> [01f70530] gles2 generic error: parent window not available
[01f6fe70] mmal_xsplitter vout display error: Failed to open Xsplitter:opengles2 module
[01f70530] xcb generic error: window not available
[01f6fe70] mmal_xsplitter vout display error: Failed to open Xsplitter:xcb_x11 module
i
Index: 0
Album: None
Title: Hello Kiss me No say-a2ZaDrdpwMk.webm
File Size: 23,840,020 bytes
Artist: None
Genre: None Track:   0/0
Path: /var/samba/DataShare/02_YouTubeDLs/05_ぜんぶ君のせいだ。/20160127_A01st_やみかわIMRAD/Hello Kiss me No say-a2ZaDrdpwMk.webm
po: POSITION, i: INDEX/Info, p: PREVIOUS, n: NEXT, q: QUIT
eq [frequency band value list]: Equalizer On/Off
>> q
Quit.

Python コードを以下のように VLC インスタンスのオプションに '--no-video' を指定して、ビデオ出力を無効にする。

    medias = walk(PATH, exts)
    medias.sort()
    print(f'Media Count: {len(medias)}')
 
    # No Video mode for headless Raspberry Pi
    vlcInstance = vlc.Instance('--no-video')
 
    #mediaList = vlc.MediaList()
    mediaList = vlcInstance.media_list_new()
 
    for index, media in enumerate(medias):
        print(f'Index: {index: >4} Path: {media}')
        mediaList.add_media(os.path.join(media[0], media[1]))
 
    #mediaListPlayer = vlc.MediaListPlayer()
    mediaListPlayer = vlcInstance.media_list_player_new()
 
    mediaListPlayer.set_media_list(mediaList)
    mediaListPlayer.set_playback_mode(vlc.PlaybackMode.loop)
    mediaListPlayer.play()

Index:   95 Path: ('/var/samba/DataShare/02_YouTubeDLs/05_ぜんぶ君のせいだ。/20210603_ARe04_Q.E.D. bi', '13.ぜんぶ君のせいだ。あおはる-hNuG-hrxzMY.mkv')
Index:   96 Path: ('/var/samba/DataShare/02_YouTubeDLs/05_ぜんぶ君のせいだ。/20210603_ARe04_Q.E.D. bi', '14.ぜんぶ君のせいだ。独唱無題-rBeoKrJFkb0.mkv')
Index:   97 Path: ('/var/samba/DataShare/02_YouTubeDLs/05_ぜんぶ君のせいだ。/20210603_ARe04_Q.E.D. bi', '15.ぜんぶ君のせいだ。無題合唱-N9tuj3sHMXI.mkv')
po: POSITION, i: INDEX/Info, p: PREVIOUS, n: NEXT, q: QUIT
eq [frequency band value list]: Equalizer On/Off
>> i
Index: 2
Album: None
Title: ぜんぶ君のせいだ。「ShitEndプラシーボ」Official MusicVideo-1CsCYcTF9w4.mp4
File Size: 64,454,373 bytes
Artist: None
Genre: None Track:   0/0
Path: /var/samba/DataShare/02_YouTubeDLs/05_ぜんぶ君のせいだ。/20160127_A01st_やみかわIMRAD/ぜんぶ君
のせいだ。「ShitEndプラシーボ」Official MusicVideo-1CsCYcTF9w4.mp4
po: POSITION, i: INDEX/Info, p: PREVIOUS, n: NEXT, q: QUIT
eq [frequency band value list]: Equalizer On/Off
>>

参考文献

python - FileNotFoundError: Could not find module 'libvlc.dll' - Stack Overflow
mp3 - Python VLC binding- playing a playlist - Stack Overflow
python-vlcで音楽再生をプログラム制御

付録

あんまし直してる暇も無いので、python-vlcでテキトーに作ったコマンドラインプレーヤーにイコライザーのセットコマンドをテキトーに追加してます😅💦 / Twitter
Python製のVLCコマンドラインプレイヤーを、Raspberry Pi Zeroでヘッドレス運用すると、動画データを含む場合にエラーを起こして動作が不安定になっていたので、ビデオ出力を無効化してオーディオ再生できるようにしました😊 / Twitter