【问题标题】:Python script fails execution on subprocess.run() call only when called from context menu仅当从上下文菜单调用时,Python 脚本才会在 subprocess.run() 调用上执行失败
【发布时间】:2019-07-31 16:50:54
【问题描述】:

我有一个 python 脚本,我想从 Windows 文件浏览器上下文菜单 (https://www.howtogeek.com/107965/how-to-add-any-application-shortcut-to-windows-explorers-context-menu/) 调用它

我目前正在调试从非特定上下文(HKEY_CLASSES_ROOT\Directory\Background\shell)使用命令“python”D:\toolbox\mineAudio.py“0”调用它 (注意python3在路径上为python,脚本在D:\toolbox\mineAudio.py)

当我从 cmd 调用脚本时,它可以使用该命令按预期工作,并且当我对脚本进行调试修改(将 os.system("pause") 添加到随机行)时,我可以验证它是否正确运行它到达meta=cmd(['ffmpeg','-i',target])(第46行)的那一点,它立即无声地失败(注意ffmpeg也在路径上)

编辑:它实际上到达第 15 行 result = subprocess.run(command, stdout=subprocess.PIPE,stderr=subprocess.PIPE,startupinfo=startupinfo) 我无法弄清楚为什么程序在那里失败,因为该行在我从上下文菜单以外的地方测试过脚本的其他任何地方都可以正常工作。

如果你想浏览它,这里是完整的脚本

import subprocess
import os
import sys
from sys import argv
from tree import tree
#for command line use:
#mineAudo.py [prompt=1] [dir=cwd]
#first arg prompt will prompt user for dir if 1, otherwise it wont
#second arg is the directory to use, if specified this will override prompt, if not and prompt=0, current working dir is used
def cmd(command):
    startupinfo = subprocess.STARTUPINFO()
    startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
    startupinfo.wShowWindow = subprocess.SW_HIDE
    result = subprocess.run(command, stdout=subprocess.PIPE,stderr=subprocess.PIPE,startupinfo=startupinfo)
    return result.stderr.decode("utf-8")
def stripStreams(meta):
    i=1;
    lines=[]
    while i>0 :
        i=meta.find("Stream",i+1)
        lineEnd=meta.find("\n",i)
        lines.append(meta[i:lineEnd])

    return lines
def mineAudio(streams):
    ret=[]
    for stream in streams:
        if "Audio:" in stream:
            start =stream.find("#")+1
            end=stream.find("(",start)
            ret.append(stream[start:end])
    return ret
def convDir(dirTarget):
    targets=tree(dirTarget)
    convList(targets,dirTarget)

def convList(targets,dirTarget):
        print(targets)
        #target="2018-05-31 06-16-39.mp4"
        i=0
        for target in targets:
            i+=1

            if(target[target.rfind("."):]==".mp4"):
                print("("+str(i)+"/"+str(len(targets))+") starting file "+target)
                meta=cmd(['ffmpeg','-i',target])
                streams=stripStreams(meta)
                streams=mineAudio(streams)
                count=0
                output=target[target.rfind("/")+1:target.rfind(".")]
                file=target[target.rfind("/")+1:]
                #print (output)
                try:
                    os.mkdir(dirTarget+"\\"+output)
                except:
                    pass
                for s in streams:
                    print("converting track "+str(count+1)+" of "+str(len(streams)));
                    count+=1
                    cmd("ffmpeg -i \""+target+"\" -vn -sn -c:a mp3 -ab 192k -map "+s+" \""+dirTarget+"\\"+output+"\\"+output+" Track "+str(count)+".mp3\"")
                print("moving "+target+" to "+dirTarget+"\\"+output+"\\"+file)
                os.rename(target,dirTarget+"\\"+output+"\\"+file)
                print("Finished file "+target)
            else:
                print("("+str(i)+"/"+str(len(targets))+") skiping non mp4 file "+target)

def prompt():
    while True:
        dirTarget=input("input target dir: ")
        convDir(dirTarget)



if __name__ == "__main__":
        sys.setrecursionlimit(2000)    
        if len(argv)>2:
                if os.path.isdir(argv[2]):
                    convDir(argv[2])
                else:
                    convList([argv[2]],os.path.dirname(argv[2]))
        elif(len(argv)>1):
                if int(argv[1])==1:
                    prompt()
                else:
                    convDir(os.getcwd())
        else:
            prompt()


        os.system("pause")

请注意,我不喜欢这个特定的实现,任何具有相同效果的实现(自动从 .mp4 文件中提取 .mp3 曲目)也可以

还有,这里是文件树

#Returns the paths of all files in a directory and all sub directories relative to start directory
import os
def tree(directory,target="f"):
    paths=[]
    for currentDir,dirs,files in os.walk(directory):
        if target=="f":
            for file in files:
                paths.append(currentDir+"/"+file)
        if target=="d":
            #paths.append(currentDir)
            for dir in dirs:
                paths.append(currentDir+"/"+dir)
    for i in range(len(paths)):
        paths[i]=paths[i].replace("\\","/")
    return paths

谁能帮我搞定这个工作?

编辑: 这是一个较短的示例代码,它以相同的方式崩溃(但仍然使用 ffmpeg)

import subprocess
import os
def cmd(command):
    startupinfo = subprocess.STARTUPINFO()
    startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
    startupinfo.wShowWindow = subprocess.SW_HIDE

    result = subprocess.run(command,stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,stderr=subprocess.PIPE,startupinfo=startupinfo)

    return result.stderr.decode("utf-8")


os.system("pause")

out=cmd(['ffmpeg','-i','D:\\ffmpeg test\\test\\2018-05-31 06-16-39\\2018-05-31 06-16-39.mp4'])
print(out)
os.system("pause")

(注意文件是硬编码的,程序输出应该是 )

【问题讨论】:

  • 我希望你相信你的文件名——恶意命名的文件有很多机会运行任意命令。无论如何——(1)如果你可以构建一个更短的minimal reproducible example(删除不是导致问题的强制性元素的代码),这将使调查更容易; (2) 请注意,ffmpeg 从标准输入读取——您可能希望显式重定向它,即使来自 subprocess.DEVNULL 或类似的。
  • 我相信文件名,但我确实明白你的意思,无论如何我都应该确保它们已经过消毒。至于最小的完整示例,应该这样做
  • 我添加了stdin=subprocess.DEVNULL,没有帮助:(
  • 我实际上并不推荐将清理作为一种方法——最好完全从代码中带外传递数据——但 Windows 没有给你这个选项(因为每个程序都传递了一个单个命令行字符串而不是 argv 数组),所以......是的。 ://
  • (如果这不是以 Windows 为中心的,我会试一试;不过,该平台超出了我的专业领域)。

标签: python python-3.x ffmpeg windows-10 subprocess


【解决方案1】:

我设法通过关于创建调用 python 脚本命令的批处理文件的 hackish 方式来“解决”问题,但这似乎有点 hack,我认为会有更好的方法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-12-20
    • 2020-01-12
    • 2011-08-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多