从mplayer读取控制台输出以解析轨道的位置/长度



当您运行mplayer时,它将通过stdout显示播放曲目的位置和长度(以及其他一些信息)。

以下是mplayer的输出示例:

MPlayer2 2.0-728-g2c378c7-4+b1 (C) 2000-2012 MPlayer Team
Cannot open file '/home/pi/.mplayer/input.conf': No such file or directory
Failed to open /home/pi/.mplayer/input.conf.
Cannot open file '/etc/mplayer/input.conf': No such file or directory
Failed to open /etc/mplayer/input.conf.
Playing Bomba Estéreo - La Boquilla [Dixone Remix].mp3.
Detected file format: MP2/3 (MPEG audio layer 2/3) (libavformat)
[mp3 @ 0x75bc15b8]max_analyze_duration 5000000 reached
[mp3 @ 0x75bc15b8]Estimating duration from bitrate, this may be inaccurate
[lavf] stream 0: audio (mp3), -aid 0
Clip info:
 album_artist: Bomba Estéreo
 genre: Latin
 title: La Boquilla [Dixone Remix]
 artist: Bomba Estéreo
 TBPM: 109
 TKEY: 11A
 album: Unknown
 date: 2011
Load subtitles in .
Selected audio codec: MPEG 1.0/2.0/2.5 layers I, II, III [mpg123]
AUDIO: 44100 Hz, 2 ch, s16le, 320.0 kbit/22.68% (ratio: 40000->176400)
AO: [pulse] 44100Hz 2ch s16le (2 bytes per sample)
Video: no video
Starting playback...
A:  47.5 (47.4) of 229.3 (03:49.3)  4.1%

最后一行(A: 47.5 (47.4) of 229.3 (03:49.3) 4.1%)是我试图读取的内容,但由于某种原因,Process.OutputDataReceived事件处理程序从未接收到它。

我是不是错过了什么?mplayer是否使用了某种非标准的方式将"A:"行输出到控制台?

以下是代码,以防有帮助:

Public Overrides Sub Play()
    player = New Process()
    player.EnableRaisingEvents = True
    With player.StartInfo
        .FileName = "mplayer"
        .Arguments = String.Format("-ss {1} -endpos {2} -volume {3} -nolirc -vc null -vo null ""{0}""",
                                   tmpFileName,
                                   mTrack.StartTime,
                                   mTrack.EndTime,
                                   100)
        .CreateNoWindow = False
        .UseShellExecute = False
        .RedirectStandardOutput = True
        .RedirectStandardError = True
        .RedirectStandardInput = True
    End With
    AddHandler player.OutputDataReceived, AddressOf DataReceived
    AddHandler player.ErrorDataReceived, AddressOf DataReceived
    AddHandler player.Exited, Sub() KillPlayer()
    player.Start()
    player.BeginOutputReadLine()
    player.BeginErrorReadLine()
    waitForPlayer.WaitOne()
    KillPlayer()
End Sub
Private Sub DataReceived(sender As Object, e As DataReceivedEventArgs)
    If e.Data = Nothing Then Exit Sub
    If e.Data.Contains("A: ") Then
        ' Parse the data
    End If
End Sub

显然,唯一的解决方案是在"slave"模式下运行mplayer,如下所述:http://www.mplayerhq.hu/DOCS/tech/slave.txt

在这种模式下,我们可以(通过stdin)向mplayer发送命令,并且响应(如果有的话)将通过stdout发送。

这里有一个非常简单的实现,可以显示mplayer的当前位置(以秒为单位):

using System;
using System.Threading;
using System.Diagnostics;
using System.Collections.Generic;
namespace TestMplayer {
    class MainClass {
        private static Process player;
        public static void Main(string[] args) {
            String fileName = "/home/pi/Documents/Projects/Raspberry/RPiPlayer/RPiPlayer/bin/Electronica/Skrillex - Make It Bun Dem (Damian Marley) [Butch Clancy Remix].mp3";
            player = new Process();
            player.EnableRaisingEvents = true;
            player.StartInfo.FileName = "mplayer";
            player.StartInfo.Arguments = String.Format("-slave -nolirc -vc null -vo null "{0}"", fileName);
            player.StartInfo.CreateNoWindow = false;
            player.StartInfo.UseShellExecute = false;
            player.StartInfo.RedirectStandardOutput = true;
            player.StartInfo.RedirectStandardError = true;
            player.StartInfo.RedirectStandardInput = true;
            player.OutputDataReceived += DataReceived;
            player.Start();
            player.BeginOutputReadLine();
            player.BeginErrorReadLine();
            Thread getPosThread = new Thread(GetPosLoop);
            getPosThread.Start();
        }
        private static void DataReceived(object o, DataReceivedEventArgs e) {
            Console.Clear();
            Console.WriteLine(e.Data);
        }
        private static void GetPosLoop() {
            do {
                Thread.Sleep(250);
                player.StandardInput.Write("get_time_pos" + Environment.NewLine);
            } while(!player.HasExited);
        }
    }
}

我在另一个或多或少以类似方式工作的应用程序(dbPowerAmp)中发现了同样的问题,在我的情况下,问题是进程输出使用Unicode编码来写入stdout缓冲区,因此,我必须将StandardOutputEncodingStandardError设置为Unicode才能开始阅读。

您的问题似乎是一样的,因为如果在您发布的输出中找不到"A",这清楚地显示了现有的"A"。那么这可能意味着在读取用于读取输出的当前编码时,字符不同。

因此,在读取进程输出时,请尝试设置正确的编码,尝试将其设置为Unicode

  • ProcessStartInfo.StandardOutputEncoding
  • ProcessStartInfo.StandardErrorEncoding

使用"读取";而不是";读取线";,并将输入视为二进制,可能会解决您的问题。

首先,是的,mplayer从属模式可能正是您想要的。但是,如果您决定解析控制台输出,这是可能的。

从属模式的存在是有原因的,如果你对在程序中使用mplayer有一半的认真态度,那么花一点时间来弄清楚如何正确使用它是值得的。也就是说,我相信在某些情况下,包装器是合适的方法。也许你想假装mplayer运行正常,并从控制台控制它,但秘密监视文件位置以稍后恢复它?包装器可能比将所有mplayers键盘命令转换为从属模式命令更容易?

你的问题很可能是你试图使用";读线";从内部蟒蛇在一条无尽的线上。输出的那一行包含\r\n而不是行分隔符,因此readline将把它视为一个无休止的行。sed也会以这种方式失败,但其他命令(如grep)在某些情况下\r\n会将其视为。

对的处理不一致,不能依赖。例如,我的grep版本将匹配IF输出视为控制台,并使用分隔输出。但如果输出是一个管道,它会将其视为任何其他字符。

例如:

mplayer TMBG-Older.mp3 2>/dev/null | tr 'r' 'n' | grep "^A: " | sed 's/^A: *([0-9.]*) .*/1/' | tail -n 1

我正在使用";tr〃;此处强制它为'\n',以便管道中的其他命令可以以一致的方式处理它。

此命令管道输出一行,仅包含以秒为单位的结束位置,并带有小数点。但是如果你去掉";tr〃;从这个管道发出命令,坏事就会发生。在我的系统上,它只显示";0.0";作为位置;sed";不能很好地处理'\r'行分隔符,并且所有位置更新都被视为同一行。

我确信python也不能很好地处理\r,这可能是您的问题。如果是,则使用";读取";而不是";读线";将其视为二进制可能是正确的解决方案。

不过,这种方法还有其他问题。缓冲是一个很大的问题^C导致该命令不输出任何内容,mplayer必须优雅地退出以显示任何内容,因为管道缓冲内容,缓冲区在SIGINT上被丢弃。

如果你真的想变得有趣,你可能会把几个输入源放在一起,用多种方式处理输出,然后真正围绕mplayer编写一个包装器。一个脆弱、复杂的包装器,每次更新mplayer、用户做了一些意外的事情,或者正在播放的文件名包含一些奇怪的东西,SIGSTOP或SIGINT时都可能会损坏。也许还有其他我没有想到的事情。

最新更新