jaseg / python-mpv

Python interface to the awesome mpv media player
https://git.jaseg.de/python-mpv.git
Other
532 stars 67 forks source link

Skip Silence #202

Closed seandenigris closed 2 years ago

seandenigris commented 2 years ago

This extension to skip silence might be of interest for adaptation and inclusion in mpv proper. Here is a usage example.

jaseg commented 2 years ago

Neat idea! Here's a slightly simpler version that uses the wait_for_event method that I've recently added on master:

#!/usr/bin/env python3
import sys
import mpv

p = mpv.MPV()
p.play(sys.argv[1])

def skip_silence():
    p.set_loglevel('debug')
    p.af = 'lavfi=[silencedetect=n=-20dB:d=1]'
    p.speed = 100
    def check(evt):
        toks = evt['event']['text'].split()
        if 'silence_end:' in toks:
            return float(toks[2])
    p.time_pos = p.wait_for_event('log_message', cond=check)
    p.speed = 1
    p.af = ''

skip_silence()
p.wait_for_playback()

I think I'll include this in a future release. The only thing I don't like about it right now is that it has to change the instance's log level. We could theoretically avoid that by cloning the MPV handle, but that might be a bit too complex to include in python-mpv.

seandenigris commented 2 years ago

Thanks for the feedback! I'm not a Python programmer, but the Smalltalk-inspired platformI use, GToolkit, supports Python and its countless cool libraries - including yours :) I cobbled it together due to necessity from SO posts, so feel free to improve/use it any way you see fit.

seandenigris commented 2 years ago

I tried to test this version, but from:

p.time_pos = p.wait_for_event('log_message', cond=check)

I'm getting TypeError: ('Tried to get/set mpv property using wrong format, or passed invalid value', -9, (<MpvHandle object at 0x10140b0c0>, b'time-pos', b'None'))

seandenigris commented 2 years ago

This is what I finally ended up with based on your feedback:

def skip_silence(self, noise=-40, duration=1):
    self.set_loglevel('v')
    endPosition = None
    def is_silence_end(evt):
        nonlocal endPosition
        toks = evt['event']['text'].split()
        if 'silence_start:' in toks:
          self.speed = 100
        elif 'silence_end:' in toks:
          endPosition = float(toks[2])
          return True
        return False
    with self.prepare_and_wait_for_event('log_message', cond=is_silence_end):
      self.af = 'lavfi=[silencedetect=n=' + str(noise) + 'dB:d=' + str(duration) + ']'
    self.time_pos = endPosition
    self.speed = 1
    self.af = ''

mpv.MPV.skip_silence = skip_silence

One thing it took me a while to figure out was that the block of with self.prepare_and_wait_for_event(...): is where you put the thing that might potentially trigger the event. It was alluded to in the source docs, but IMHO could use some clarification. Also, prepare_and_wait_for_event seems to return a value, but I couldn't figure out how to access it. I tried to put an assignment to the left of the with but got a syntax error.

jaseg commented 2 years ago

Thank you for the example! The README now contains this code in the example usage section. I will publish a release soon that contains the new futures API that is used there.