Tell user to install ffmpeg if missing (#117)

Tell user to install ffmpeg if missing
pull/357/head
nyanpasu64 2019-01-04 19:00:05 -08:00 zatwierdzone przez GitHub
rodzic b29e5e9252
commit fd24ab6b0d
7 zmienionych plików z 129 dodań i 20 usunięć

Wyświetl plik

@ -8,11 +8,13 @@ from PyInstaller.building.datastruct import TOC
block_cipher = None
dir = "corrscope/gui"
includes = glob.glob("corrscope/gui/*.ui")
assert includes
def keep(dir, wildcard):
includes = glob.glob(f"{dir}/{wildcard}")
assert includes
return [(include, dir) for include in includes]
datas = [(include, dir) for include in includes]
datas = keep("corrscope/gui", "*.ui") + keep("corrscope/path", "*")
a = Analysis(
["corrscope\\__main__.py"],

Wyświetl plik

@ -1,4 +1,5 @@
import datetime
import sys
from itertools import count
from pathlib import Path
from typing import Optional, List, Tuple, Union, Iterator
@ -8,6 +9,7 @@ import click
from corrscope import __version__
from corrscope.channel import ChannelConfig
from corrscope.config import yaml
from corrscope.ffmpeg_path import MissingFFmpegError
from corrscope.outputs import IOutputConfig, FFplayOutputConfig, FFmpegOutputConfig
from corrscope.corrscope import default_config, CorrScope, Config, Arguments
@ -211,7 +213,11 @@ def main(
profile_dump_name = get_profile_dump_name(first_song_name)
cProfile.runctx('command()', globals(), locals(), profile_dump_name)
else:
command()
try:
command()
except MissingFFmpegError as e:
# Tell user how to install ffmpeg (__str__).
print(e, file=sys.stderr)
def get_profile_dump_name(prefix: str) -> str:

Wyświetl plik

@ -0,0 +1,50 @@
import os
import platform
import sys
from pathlib import Path
from corrscope.config import CorrError
# Add app-specific ffmpeg path.
path_dir = str(Path(__file__).parent / "path")
os.environ["PATH"] += os.pathsep + path_dir
# Editing sys.path doesn't work.
# https://bugs.python.org/issue8557 is relevant but may be outdated.
# Unused
# def ffmpeg_exists():
# return shutil.which("ffmpeg") is not None
def get_ffmpeg_url() -> str:
# is_python_64 = sys.maxsize > 2 ** 32
is_os_64 = platform.machine().endswith("64")
def url(os_ver):
return f"https://ffmpeg.zeranoe.com/builds/{os_ver}/shared/ffmpeg-latest-{os_ver}-shared.zip"
if sys.platform == "win32" and is_os_64:
return url("win64")
elif sys.platform == "win32" and not is_os_64:
return url("win32")
elif sys.platform == "darwin" and is_os_64:
return url("macos64")
else:
return ""
class MissingFFmpegError(CorrError):
ffmpeg_url = get_ffmpeg_url()
can_download = bool(ffmpeg_url)
message = f'FFmpeg must be in PATH or "{path_dir}" in order to use corrscope.\n'
if can_download:
message += f"Download ffmpeg from {ffmpeg_url}."
else:
message += "Cannot download FFmpeg for your platform."
def __str__(self):
return self.message

Wyświetl plik

@ -15,8 +15,10 @@ from PyQt5.QtWidgets import QShortcut
from corrscope import __version__ # variable
from corrscope import cli # module wtf?
from corrscope import ffmpeg_path
from corrscope.channel import ChannelConfig
from corrscope.config import CorrError, copy_config, yaml
from corrscope.corrscope import CorrScope, Config, Arguments, default_config
from corrscope.gui.data_bind import (
PresentationModel,
map_gui,
@ -32,7 +34,6 @@ from corrscope.gui.util import (
TracebackDialog,
)
from corrscope.outputs import IOutputConfig, FFplayOutputConfig, FFmpegOutputConfig
from corrscope.corrscope import CorrScope, Config, Arguments, default_config
from corrscope.triggers import CorrelationTriggerConfig, ITriggerConfig
from corrscope.util import obj_name
@ -336,15 +337,19 @@ class MainWindow(qw.QMainWindow):
cfg = copy_config(self.model.cfg)
t = self.corr_thread.obj = CorrThread(cfg, arg)
t.error.connect(self.on_play_thread_error)
t.finished.connect(self.on_play_thread_finished)
t.error.connect(self.on_play_thread_error)
t.ffmpeg_missing.connect(self.on_play_thread_ffmpeg_missing)
t.start()
def on_play_thread_finished(self):
self.corr_thread.set(None)
def on_play_thread_error(self, stack_trace: str):
TracebackDialog(self).showMessage(stack_trace)
def on_play_thread_finished(self):
self.corr_thread.set(None)
def on_play_thread_ffmpeg_missing(self):
DownloadFFmpegActivity(self)
def _get_args(self, outputs: List[IOutputConfig]):
arg = Arguments(cfg_dir=self.cfg_dir, outputs=outputs)
@ -401,14 +406,24 @@ class CorrThread(qc.QThread):
arg = self.arg
try:
CorrScope(cfg, arg).play()
except Exception:
except ffmpeg_path.MissingFFmpegError:
arg.on_end()
stack_trace = traceback.format_exc()
self.ffmpeg_missing.emit()
except Exception as e:
arg.on_end()
if isinstance(e, CorrError):
stack_trace = traceback.format_exc(limit=0)
else:
stack_trace = traceback.format_exc()
self.error.emit(stack_trace)
else:
arg.on_end()
error = qc.pyqtSignal(str)
ffmpeg_missing = qc.pyqtSignal()
class CorrProgressDialog(qw.QProgressDialog):
@ -626,7 +641,6 @@ class ChannelModel(qc.QAbstractTableModel):
Column("trigger_width", int, None, "Trigger Width ×"),
Column("render_width", int, None, "Render Width ×"),
Column("line_color", str, None, "Line Color"),
# TODO move from table view to sidebar QDataWidgetMapper?
Column("trigger__edge_strength", float, None),
Column("trigger__responsiveness", float, None),
Column("trigger__buffer_falloff", float, None),
@ -799,3 +813,32 @@ class ChannelModel(qc.QAbstractTableModel):
nope = qc.QVariant()
class DownloadFFmpegActivity:
title = "Missing FFmpeg"
ffmpeg_url = ffmpeg_path.get_ffmpeg_url()
can_download = bool(ffmpeg_url)
path_uri = qc.QUrl.fromLocalFile(ffmpeg_path.path_dir).toString()
required = (
f"FFmpeg must be in PATH or "
f'<a href="{path_uri}">corrscope folder</a> in order to use corrscope.<br>'
)
ffmpeg_template = required + (
f'Download ffmpeg from <a href="{ffmpeg_url}">{ffmpeg_url}</a>.'
)
fail_template = required + "Cannot download FFmpeg for your platform."
def __init__(self, window: qw.QWidget):
"""Prompt the user to download and install ffmpeg."""
Msg = qw.QMessageBox
if not self.can_download:
Msg.information(window, self.title, self.fail_template, Msg.Ok)
return
Msg.information(window, self.title, self.ffmpeg_template, Msg.Ok)

Wyświetl plik

@ -1,12 +1,13 @@
# https://ffmpeg.org/ffplay.html
import numpy as np
import shlex
import subprocess
from abc import ABC, abstractmethod
from os.path import abspath
from typing import TYPE_CHECKING, Type, List, Union, Optional
import numpy as np
from corrscope.config import register_config
from corrscope.ffmpeg_path import MissingFFmpegError
if TYPE_CHECKING:
from corrscope.corrscope import Config
@ -95,12 +96,16 @@ class _FFmpegProcess:
self.templates.append(cfg.audio_template) # audio
def popen(self, extra_args, bufsize, **kwargs) -> subprocess.Popen:
return subprocess.Popen(
self._generate_args() + extra_args,
stdin=subprocess.PIPE,
bufsize=bufsize,
**kwargs,
)
"""Raises FileNotFoundError if FFmpeg missing"""
try:
args = self._generate_args() + extra_args
return subprocess.Popen(
args, stdin=subprocess.PIPE, bufsize=bufsize, **kwargs
)
except FileNotFoundError as e:
raise MissingFFmpegError(
# FIXME REMOVE f'Class {obj_name(self)}: program {args[0]} is missing'
)
def _generate_args(self) -> List[str]:
return [arg for template in self.templates for arg in shlex.split(template)]

3
corrscope/path/.gitignore vendored 100644
Wyświetl plik

@ -0,0 +1,3 @@
*
!*.txt
!.gitignore