kopia lustrzana https://github.com/corrscope/corrscope
rodzic
b29e5e9252
commit
fd24ab6b0d
|
@ -8,11 +8,13 @@ from PyInstaller.building.datastruct import TOC
|
||||||
block_cipher = None
|
block_cipher = None
|
||||||
|
|
||||||
|
|
||||||
dir = "corrscope/gui"
|
def keep(dir, wildcard):
|
||||||
includes = glob.glob("corrscope/gui/*.ui")
|
includes = glob.glob(f"{dir}/{wildcard}")
|
||||||
assert includes
|
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(
|
a = Analysis(
|
||||||
["corrscope\\__main__.py"],
|
["corrscope\\__main__.py"],
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import datetime
|
import datetime
|
||||||
|
import sys
|
||||||
from itertools import count
|
from itertools import count
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, List, Tuple, Union, Iterator
|
from typing import Optional, List, Tuple, Union, Iterator
|
||||||
|
@ -8,6 +9,7 @@ import click
|
||||||
from corrscope import __version__
|
from corrscope import __version__
|
||||||
from corrscope.channel import ChannelConfig
|
from corrscope.channel import ChannelConfig
|
||||||
from corrscope.config import yaml
|
from corrscope.config import yaml
|
||||||
|
from corrscope.ffmpeg_path import MissingFFmpegError
|
||||||
from corrscope.outputs import IOutputConfig, FFplayOutputConfig, FFmpegOutputConfig
|
from corrscope.outputs import IOutputConfig, FFplayOutputConfig, FFmpegOutputConfig
|
||||||
from corrscope.corrscope import default_config, CorrScope, Config, Arguments
|
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)
|
profile_dump_name = get_profile_dump_name(first_song_name)
|
||||||
cProfile.runctx('command()', globals(), locals(), profile_dump_name)
|
cProfile.runctx('command()', globals(), locals(), profile_dump_name)
|
||||||
else:
|
else:
|
||||||
|
try:
|
||||||
command()
|
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:
|
def get_profile_dump_name(prefix: str) -> str:
|
||||||
|
|
|
@ -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
|
|
@ -15,8 +15,10 @@ from PyQt5.QtWidgets import QShortcut
|
||||||
|
|
||||||
from corrscope import __version__ # variable
|
from corrscope import __version__ # variable
|
||||||
from corrscope import cli # module wtf?
|
from corrscope import cli # module wtf?
|
||||||
|
from corrscope import ffmpeg_path
|
||||||
from corrscope.channel import ChannelConfig
|
from corrscope.channel import ChannelConfig
|
||||||
from corrscope.config import CorrError, copy_config, yaml
|
from corrscope.config import CorrError, copy_config, yaml
|
||||||
|
from corrscope.corrscope import CorrScope, Config, Arguments, default_config
|
||||||
from corrscope.gui.data_bind import (
|
from corrscope.gui.data_bind import (
|
||||||
PresentationModel,
|
PresentationModel,
|
||||||
map_gui,
|
map_gui,
|
||||||
|
@ -32,7 +34,6 @@ from corrscope.gui.util import (
|
||||||
TracebackDialog,
|
TracebackDialog,
|
||||||
)
|
)
|
||||||
from corrscope.outputs import IOutputConfig, FFplayOutputConfig, FFmpegOutputConfig
|
from corrscope.outputs import IOutputConfig, FFplayOutputConfig, FFmpegOutputConfig
|
||||||
from corrscope.corrscope import CorrScope, Config, Arguments, default_config
|
|
||||||
from corrscope.triggers import CorrelationTriggerConfig, ITriggerConfig
|
from corrscope.triggers import CorrelationTriggerConfig, ITriggerConfig
|
||||||
from corrscope.util import obj_name
|
from corrscope.util import obj_name
|
||||||
|
|
||||||
|
@ -336,15 +337,19 @@ class MainWindow(qw.QMainWindow):
|
||||||
|
|
||||||
cfg = copy_config(self.model.cfg)
|
cfg = copy_config(self.model.cfg)
|
||||||
t = self.corr_thread.obj = CorrThread(cfg, arg)
|
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.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()
|
t.start()
|
||||||
|
|
||||||
|
def on_play_thread_finished(self):
|
||||||
|
self.corr_thread.set(None)
|
||||||
|
|
||||||
def on_play_thread_error(self, stack_trace: str):
|
def on_play_thread_error(self, stack_trace: str):
|
||||||
TracebackDialog(self).showMessage(stack_trace)
|
TracebackDialog(self).showMessage(stack_trace)
|
||||||
|
|
||||||
def on_play_thread_finished(self):
|
def on_play_thread_ffmpeg_missing(self):
|
||||||
self.corr_thread.set(None)
|
DownloadFFmpegActivity(self)
|
||||||
|
|
||||||
def _get_args(self, outputs: List[IOutputConfig]):
|
def _get_args(self, outputs: List[IOutputConfig]):
|
||||||
arg = Arguments(cfg_dir=self.cfg_dir, outputs=outputs)
|
arg = Arguments(cfg_dir=self.cfg_dir, outputs=outputs)
|
||||||
|
@ -401,14 +406,24 @@ class CorrThread(qc.QThread):
|
||||||
arg = self.arg
|
arg = self.arg
|
||||||
try:
|
try:
|
||||||
CorrScope(cfg, arg).play()
|
CorrScope(cfg, arg).play()
|
||||||
except Exception:
|
|
||||||
|
except ffmpeg_path.MissingFFmpegError:
|
||||||
arg.on_end()
|
arg.on_end()
|
||||||
|
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()
|
stack_trace = traceback.format_exc()
|
||||||
self.error.emit(stack_trace)
|
self.error.emit(stack_trace)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
arg.on_end()
|
arg.on_end()
|
||||||
|
|
||||||
error = qc.pyqtSignal(str)
|
error = qc.pyqtSignal(str)
|
||||||
|
ffmpeg_missing = qc.pyqtSignal()
|
||||||
|
|
||||||
|
|
||||||
class CorrProgressDialog(qw.QProgressDialog):
|
class CorrProgressDialog(qw.QProgressDialog):
|
||||||
|
@ -626,7 +641,6 @@ class ChannelModel(qc.QAbstractTableModel):
|
||||||
Column("trigger_width", int, None, "Trigger Width ×"),
|
Column("trigger_width", int, None, "Trigger Width ×"),
|
||||||
Column("render_width", int, None, "Render Width ×"),
|
Column("render_width", int, None, "Render Width ×"),
|
||||||
Column("line_color", str, None, "Line Color"),
|
Column("line_color", str, None, "Line Color"),
|
||||||
# TODO move from table view to sidebar QDataWidgetMapper?
|
|
||||||
Column("trigger__edge_strength", float, None),
|
Column("trigger__edge_strength", float, None),
|
||||||
Column("trigger__responsiveness", float, None),
|
Column("trigger__responsiveness", float, None),
|
||||||
Column("trigger__buffer_falloff", float, None),
|
Column("trigger__buffer_falloff", float, None),
|
||||||
|
@ -799,3 +813,32 @@ class ChannelModel(qc.QAbstractTableModel):
|
||||||
|
|
||||||
|
|
||||||
nope = qc.QVariant()
|
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)
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
# https://ffmpeg.org/ffplay.html
|
|
||||||
import numpy as np
|
|
||||||
import shlex
|
import shlex
|
||||||
import subprocess
|
import subprocess
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from os.path import abspath
|
from os.path import abspath
|
||||||
from typing import TYPE_CHECKING, Type, List, Union, Optional
|
from typing import TYPE_CHECKING, Type, List, Union, Optional
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
from corrscope.config import register_config
|
from corrscope.config import register_config
|
||||||
|
from corrscope.ffmpeg_path import MissingFFmpegError
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from corrscope.corrscope import Config
|
from corrscope.corrscope import Config
|
||||||
|
@ -95,11 +96,15 @@ class _FFmpegProcess:
|
||||||
self.templates.append(cfg.audio_template) # audio
|
self.templates.append(cfg.audio_template) # audio
|
||||||
|
|
||||||
def popen(self, extra_args, bufsize, **kwargs) -> subprocess.Popen:
|
def popen(self, extra_args, bufsize, **kwargs) -> subprocess.Popen:
|
||||||
|
"""Raises FileNotFoundError if FFmpeg missing"""
|
||||||
|
try:
|
||||||
|
args = self._generate_args() + extra_args
|
||||||
return subprocess.Popen(
|
return subprocess.Popen(
|
||||||
self._generate_args() + extra_args,
|
args, stdin=subprocess.PIPE, bufsize=bufsize, **kwargs
|
||||||
stdin=subprocess.PIPE,
|
)
|
||||||
bufsize=bufsize,
|
except FileNotFoundError as e:
|
||||||
**kwargs,
|
raise MissingFFmpegError(
|
||||||
|
# FIXME REMOVE f'Class {obj_name(self)}: program {args[0]} is missing'
|
||||||
)
|
)
|
||||||
|
|
||||||
def _generate_args(self) -> List[str]:
|
def _generate_args(self) -> List[str]:
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
*
|
||||||
|
!*.txt
|
||||||
|
!.gitignore
|
Ładowanie…
Reference in New Issue