需求:编写一个python脚本,使用第三方中控或APP控制电脑媒体软件
准备:
- VLC播放器(下载地址:官方下载:VLC media player,最棒的开源播放器 - VideoLAN)
- 安装python环境(下载地址:Download Python | Python.org) 和 IDE(spyder 或 pycharm)
- HTTP调试工具(postman)
具体步骤:
-
安装VLC播放器
-
配置VLC开启telnet端口(本地地址:127.0.0.1,端口号4212)


-
启动电脑telnet端口,需重启电脑
-
通过电脑-高级系统设置--系统属性--环境变量--PATH--编辑、新建添加VLC路径(需要重启命令行工具)
-
防火墙——高级设置——入站规则——新建-编辑——添加4212端口
-
测试监测指令:
-
telnet 127.0.0.1 4212 登录VLC,登录后可以通过play pause stop 进行控制
-
netstat -an | findstr 4212 端口监测
-
-
配置HTTP端口,VLC的HTTP端口默认8080,若要修改需要通过配置文件,文件目录:C:\Users\25207\AppData\Roaming\vlc(根据自己的目录修改)
-
端口监测指令:netstat -an | findstr 8080
-
通过spyder 运行脚本,测试验证telnet指令

-
通过postman测试验证http指令
测试使用py脚本:
# -*- coding: utf-8 -*-
"""
VLC Telnet/HTTP控制器 - 修复字节字符串问题
"""
import socket
import os
import time
import logging
import urllib.parse
import sys
import threading
import re
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import urlparse, parse_qs
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger("VLCTelnetControl")
# 默认配置
DEFAULT_PLAYER_IP = "127.0.0.1"
DEFAULT_PLAYER_PORT = 4212
DEFAULT_VLC_PASSWORD = "admin"
DEFAULT_HTTP_IP = "0.0.0.0"
DEFAULT_HTTP_PORT = 8080
DEFAULT_MEDIA_DIR = r"C:\Users\25207\Desktop" # 默认媒体文件目录
# 全局变量
PLAYER_IP = DEFAULT_PLAYER_IP
PLAYER_PORT = DEFAULT_PLAYER_PORT
VLC_PASSWORD = DEFAULT_VLC_PASSWORD
HTTP_IP = DEFAULT_HTTP_IP
HTTP_PORT = DEFAULT_HTTP_PORT
MEDIA_DIR = DEFAULT_MEDIA_DIR # 媒体文件目录
MEDIA_FILES = {} # 存储媒体文件路径 {文件名: 完整路径}
CURRENT_MEDIA = None
PLAYER_STATE = "stopped" # 状态: stopped, playing, paused
class VLCController:
@staticmethod
def send_command(command, media_path=None):
"""发送控制指令并处理响应"""
global CURRENT_MEDIA, PLAYER_STATE
s = VLCController.create_socket()
if not s:
return False
try:
# 命令映射
command_map = {
"pause": "pause",
"stop": "stop",
"replay": "seek 0",
"volup": "volume +50",
"voldown": "volume -50"
}
# 处理播放命令
if command.startswith("play"):
# 解析文件名
parts = command.split(maxsplit=1)
filename = parts[1] if len(parts) > 1 else None
# 如果没有指定文件,但提供了media_path参数
if not filename and media_path:
filename = os.path.basename(media_path)
# 如果没有指定文件,使用当前媒体
if not filename and CURRENT_MEDIA:
filename = os.path.basename(CURRENT_MEDIA)
# 如果还没有媒体文件,返回错误
if not filename:
logger.error("播放命令需要指定媒体文件")
return False
# 获取完整路径
full_path = VLCController.get_media_path(filename)
if not full_path:
logger.error(f"找不到媒体文件: {filename}")
return False
# 获取绝对路径并转换为VLC兼容格式
abs_path = os.path.abspath(full_path)
# 检查文件是否存在
if not os.path.exists(abs_path):
logger.error(f"文件不存在: {abs_path}")
return False
# 使用URL编码确保特殊字符正确处理
unix_style_path = abs_path.replace('\\', '/')
encoded_path = urllib.parse.quote(unix_style_path)
full_url = f"file:///{encoded_path}"
# 根据当前状态决定命令
if PLAYER_STATE == "stopped" or CURRENT_MEDIA != abs_path:
# 停止状态或切换媒体 - 使用play命令加载并播放
play_command = f'play "{full_url}"'
logger.info(f"发送命令: {play_command}")
s.sendall((play_command + "\n").encode('utf-8'))
PLAYER_STATE = "playing"
CURRENT_MEDIA = abs_path
elif PLAYER_STATE == "paused":
# 暂停状态 - 使用pause命令恢复播放
logger.info(f"发送命令: pause")
s.sendall(b"pause\n")
PLAYER_STATE = "playing"
else:
# 已经在播放 - 只需发送播放命令
logger.info(f"发送命令: play")
s.sendall(b"play\n")
# 处理停止命令
elif command == "stop":
logger.info(f"发送命令: stop")
s.sendall(b"stop\n")
PLAYER_STATE = "stopped"
# 处理暂停命令
elif command == "pause":
logger.info(f"发送命令: pause")
s.sendall(b"pause\n")
# 切换状态
if PLAYER_STATE == "playing":
PLAYER_STATE = "paused"
elif PLAYER_STATE == "paused":
PLAYER_STATE = "playing"
# 处理其他命令
elif command in command_map:
vlc_command = command_map[command]
logger.info(f"发送命令: {vlc_command}")
s.sendall((vlc_command + "\n").encode('utf-8'))
# 特殊命令处理
if command == "replay" and PLAYER_STATE != "stopped":
PLAYER_STATE = "playing"
# 接收响应
response = b""
while True:
try:
chunk = s.recv(1024)
if not chunk:
break
response += chunk
if b"> " in response:
break
except socket.timeout:
logger.warning("接收响应超时")
break
try:
decoded_response = response.decode('utf-8', errors='replace')
logger.info(f"响应: {decoded_response}")
except UnicodeDecodeError:
decoded_response = response.decode('latin-1', errors='replace')
logger.info(f"响应(latin-1): {decoded_response}")
# 检查错误
if b"error" in response.lower():
logger.error(f"命令执行错误: {response}")
return False
return True
except Exception as e:
logger.error(f"发送指令失败: {e}")
return False
finally:
s.close()
@staticmethod
def create_socket():
"""创建并认证Telnet连接"""
global PLAYER_IP, PLAYER_PORT, VLC_PASSWORD
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(5) # 设置超时时间
s.connect((PLAYER_IP, PLAYER_PORT))
# 接收初始响应
response = b""
while True:
chunk = s.recv(1024)
if not chunk:
break
response += chunk
if b"Password:" in response or b"> " in response:
break
try:
decoded_response = response.decode('utf-8', errors='replace')
logger.info(f"初始响应: {decoded_response}")
except UnicodeDecodeError:
decoded_response = response.decode('latin-1', errors='replace')
logger.info(f"初始响应(latin-1): {decoded_response}")
# 检查是否需要密码认证
if b"Password:" in response:
# 发送密码认证
s.sendall((VLC_PASSWORD + "\n").encode('utf-8'))
# 接收认证响应
auth_response = b""
while True:
chunk = s.recv(1024)
if not chunk:
break
auth_response += chunk
if b"> " in auth_response:
break
try:
decoded_auth = auth_response.decode('utf-8', errors='replace')
logger.info(f"认证响应: {decoded_auth}")
except UnicodeDecodeError:
decoded_auth = auth_response.decode('latin-1', errors='replace')
logger.info(f"认证响应(latin-1): {decoded_auth}")
# 检查认证是否成功
if b"> " not in auth_response:
logger.error("认证失败! 请检查密码是否正确")
s.close()
return None
else:
logger.info("认证成功!")
return s
except Exception as e:
logger.error(f"无法连接到播放器: {e}")
return None
@staticmethod
def get_media_path(filename):
"""获取媒体文件的完整路径"""
global MEDIA_DIR, MEDIA_FILES
# 如果文件名是完整路径,直接返回
if os.path.isabs(filename):
return filename
# 如果文件名在MEDIA_FILES字典中,返回对应路径
if filename in MEDIA_FILES:
return MEDIA_FILES[filename]
# 尝试在媒体目录中查找
full_path = os.path.join(MEDIA_DIR, filename)
if os.path.exists(full_path):
return full_path
# 尝试在媒体目录中模糊匹配
for file in os.listdir(MEDIA_DIR):
if file.lower().startswith(filename.lower()):
return os.path.join(MEDIA_DIR, file)
# 找不到文件
return None
def scan_media_files():
"""扫描媒体目录中的文件"""
global MEDIA_DIR, MEDIA_FILES
MEDIA_FILES = {}
if not os.path.exists(MEDIA_DIR):
return
for file in os.listdir(MEDIA_DIR):
if file.lower().endswith(('.mp4', '.avi', '.mkv', '.mov', '.mp3', '.wav')):
MEDIA_FILES[file] = os.path.join(MEDIA_DIR, file)
logger.info(f"扫描到 {len(MEDIA_FILES)} 个媒体文件")
class VLCRequestHandler(BaseHTTPRequestHandler):
"""HTTP请求处理程序"""
def do_GET(self):
"""处理GET请求"""
# 解析请求路径和查询参数
parsed = urlparse(self.path)
path = parsed.path.lower()
query = parse_qs(parsed.query)
# 映射路径到命令
command_map = {
"/play": "play",
"/pause": "pause",
"/stop": "stop",
"/replay": "replay",
"/volup": "volup",
"/voldown": "voldown",
"/list": "list"
}
# 处理媒体文件列表请求
if path == "/list":
self.send_response(200)
self.send_header('Content-type', 'text/plain; charset=utf-8')
self.end_headers()
files = "\n".join(MEDIA_FILES.keys())
self.wfile.write(f"可用媒体文件:\n{files}".encode('utf-8'))
return
# 处理播放命令
if path == "/play":
# 获取文件名参数
file_param = query.get('file', [''])[0]
# 如果没有指定文件,使用当前媒体
if not file_param and CURRENT_MEDIA:
file_param = os.path.basename(CURRENT_MEDIA)
# 如果还没有媒体文件,返回错误
if not file_param:
self.send_response(400)
self.send_header('Content-type', 'text/plain; charset=utf-8')
self.end_headers()
self.wfile.write("请指定媒体文件,例如: /play?file=video.mp4".encode('utf-8'))
return
# 获取完整路径
full_path = VLCController.get_media_path(file_param)
if not full_path:
self.send_response(404)
self.send_header('Content-type', 'text/plain; charset=utf-8')
self.end_headers()
self.wfile.write(f"找不到媒体文件: {file_param}".encode('utf-8'))
return
# 执行播放命令
success = VLCController.send_command(f"play {file_param}")
if success:
self.send_response(200)
self.send_header('Content-type', 'text/plain; charset=utf-8')
self.end_headers()
self.wfile.write(f"正在播放: {file_param}".encode('utf-8'))
else:
self.send_response(500)
self.send_header('Content-type', 'text/plain; charset=utf-8')
self.end_headers()
self.wfile.write(f"播放失败: {file_param}".encode('utf-8'))
return
# 处理其他命令
if path in command_map:
command = command_map[path]
success = VLCController.send_command(command)
if success:
self.send_response(200)
self.send_header('Content-type', 'text/plain; charset=utf-8')
self.end_headers()
self.wfile.write(f"命令 '{command}' 执行成功".encode('utf-8'))
else:
self.send_response(500)
self.send_header('Content-type', 'text/plain; charset=utf-8')
self.end_headers()
self.wfile.write(f"命令 '{command}' 执行失败".encode('utf-8'))
else:
self.send_response(404)
self.send_header('Content-type', 'text/plain; charset=utf-8')
self.end_headers()
self.wfile.write("无效命令. 可用命令: /play, /pause, /stop, /replay, /volup, /voldown, /list".encode('utf-8'))
def log_message(self, format, *args):
"""重写日志方法"""
logger.info("%s - - [%s] %s" % (self.address_string(), self.log_date_time_string(), format % args))
def start_http_server():
"""启动HTTP服务器"""
global HTTP_IP, HTTP_PORT
server_address = (HTTP_IP, HTTP_PORT)
httpd = HTTPServer(server_address, VLCRequestHandler)
logger.info(f"HTTP服务器启动,监听地址: http://{HTTP_IP}:{HTTP_PORT}")
logger.info("可用命令:")
logger.info(" /play?file=视频文件 - 播放指定视频")
logger.info(" /pause - 暂停")
logger.info(" /stop - 停止")
logger.info(" /replay - 重新播放")
logger.info(" /volup - 音量增加")
logger.info(" /voldown - 音量减少")
logger.info(" /list - 列出可用媒体文件")
try:
httpd.serve_forever()
except KeyboardInterrupt:
pass
except Exception as e:
logger.error(f"HTTP服务器错误: {e}")
finally:
httpd.server_close()
logger.info("HTTP服务器已停止")
def main():
global PLAYER_IP, PLAYER_PORT, VLC_PASSWORD, HTTP_IP, HTTP_PORT, MEDIA_DIR
# 获取用户输入
print("=" * 50)
print("VLC控制器配置")
print("=" * 50)
# 获取VLC播放器IP
player_ip = input(f"请输入VLC播放器IP地址 [默认: {PLAYER_IP}]: ").strip()
if player_ip:
PLAYER_IP = player_ip
# 获取VLC播放器密码
vlc_password = input(f"请输入VLC Telnet密码 [默认: {VLC_PASSWORD}]: ").strip()
if vlc_password:
VLC_PASSWORD = vlc_password
# 获取媒体目录
media_dir = input(f"请输入媒体文件目录 [默认: {MEDIA_DIR}]: ").strip()
if media_dir:
MEDIA_DIR = media_dir
# 检查媒体目录是否存在
if not os.path.exists(MEDIA_DIR):
logger.error(f"错误:媒体目录 '{MEDIA_DIR}' 不存在。")
logger.error("请检查目录路径是否正确")
sys.exit(1)
# 扫描媒体文件
scan_media_files()
if not MEDIA_FILES:
logger.warning(f"在目录 '{MEDIA_DIR}' 中没有找到媒体文件")
else:
logger.info(f"找到 {len(MEDIA_FILES)} 个媒体文件")
for i, file in enumerate(list(MEDIA_FILES.keys())[:5], 1):
logger.info(f" {i}. {file}")
if len(MEDIA_FILES) > 5:
logger.info(f" ... 共 {len(MEDIA_FILES)} 个文件")
# 获取HTTP服务器配置
print("\nHTTP服务器配置 (用于Loxone控制)")
http_ip = input(f"请输入HTTP服务器监听IP [默认: {HTTP_IP}]: ").strip()
if http_ip:
HTTP_IP = http_ip
http_port = input(f"请输入HTTP服务器端口 [默认: {HTTP_PORT}]: ").strip()
if http_port:
try:
HTTP_PORT = int(http_port)
except ValueError:
logger.error("端口必须是数字,使用默认端口8080")
HTTP_PORT = DEFAULT_HTTP_PORT
# 启动HTTP服务器线程
http_thread = threading.Thread(target=start_http_server, daemon=True)
http_thread.start()
# 主控制循环
while True:
print("\n可用命令:")
print(" play [文件名] - 播放指定媒体文件")
print(" pause - 暂停播放")
print(" stop - 停止播放")
print(" replay - 重新播放")
print(" volup - 音量增加")
print(" voldown - 音量减少")
print(" list - 列出可用媒体文件")
print(f"Loxone控制: http://<本机IP>:{HTTP_PORT}/play?file=文件名")
print("输入 'exit' 退出程序")
user_input = input("请输入命令: ").strip().lower()
if not user_input:
continue
if user_input == 'exit':
logger.info("正在退出程序...")
break
# 处理列表命令
if user_input == 'list':
print("\n可用媒体文件:")
for file in MEDIA_FILES.keys():
print(f" - {file}")
print(f"共 {len(MEDIA_FILES)} 个文件")
continue
# 处理播放命令(可能带文件名)
if user_input.startswith("play"):
VLCController.send_command(user_input)
logger.info(f"当前状态: {PLAYER_STATE}")
elif user_input in ['pause', 'stop', 'replay', 'volup', 'voldown']:
VLCController.send_command(user_input)
logger.info(f"当前状态: {PLAYER_STATE}")
else:
print("未知命令,请重新输入。")
if __name__ == "__main__":
main()
后续迭代版本新增功能:V1.3
VLC Telnet/HTTP控制器 -
1、增加上一个、下一个、播放列表
2、增加运行脚本自动添加媒体文件到播放列表
3、增加播放自动全屏
4、增加开机自启动脚本,并打开VLC播放器
5、修复指定播放视频文件,同时支持使用play_index指定播放列表索引文件

1122

被折叠的 条评论
为什么被折叠?



