Skip to content

Commit 2d6cac7

Browse files
fix: Premature link success message (#220)
1 parent 4e51283 commit 2d6cac7

20 files changed

+364
-474
lines changed

proxima/__init__.py

+7
Original file line numberDiff line numberDiff line change
@@ -1 +1,8 @@
11
__version__ = "0.0.1"
2+
from .app.utils import core
3+
from .app import broker
4+
from .app import checks
5+
from .app import exceptions
6+
from .queuer import resolve
7+
from .queuer.link import ProxyLinker
8+
from .queuer import handlers

proxima/app/broker.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from rich.live import Live
1616
from cryptohash import sha1
1717

18-
from proxima.settings.manager import SettingsManager
18+
from proxima.settings import SettingsManager
1919

2020

2121
class RedisConnection:

proxima/app/checks.py

+4-7
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,13 @@
33
import time
44
from typing import Union
55

6-
from rich import print
6+
from proxima import core
7+
from proxima.app.utils import pkg_info
8+
from proxima.settings import SettingsManager
9+
from proxima.worker import celery_app
710
from rich.prompt import Confirm
811
from yaspin import yaspin
912

10-
from .utils import pkg_info
11-
12-
from ..app.utils import core
13-
from ..settings.manager import SettingsManager
14-
from ..worker.celery import app as celery_app
15-
1613
settings = SettingsManager()
1714

1815
core.install_rich_tracebacks()

proxima/app/cli.py

+12-37
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from rich.console import Console
1313
from rich.rule import Rule
1414

15-
from .utils import pkg_info
15+
from proxima.app.utils import pkg_info
1616

1717
# Init classes
1818
cli_app = typer.Typer()
@@ -72,8 +72,8 @@ def draw_banner():
7272
def run_checks():
7373
"""Run before CLI App load."""
7474

75-
from ..app import checks
76-
from ..settings.manager import SettingsManager
75+
from proxima import checks
76+
from proxima.settings import SettingsManager
7777

7878
settings = SettingsManager()
7979

@@ -103,13 +103,13 @@ def queue():
103103
"""
104104

105105
# Init
106-
from ..app import checks
107-
from ..settings.manager import SettingsManager
108-
from .utils.core import setup_rich_logging
106+
from proxima import checks
107+
from proxima.settings import SettingsManager
108+
from proxima import core
109109

110110
settings = SettingsManager()
111111

112-
setup_rich_logging()
112+
core.setup_rich_logging()
113113
logger = logging.getLogger(__name__)
114114
logger.setLevel(settings["app"]["loglevel"])
115115
# End init
@@ -123,38 +123,11 @@ def queue():
123123
)
124124
print("\n")
125125

126-
from ..queuer import queue
126+
from proxima.queuer import queue
127127

128128
queue.main()
129129

130130

131-
@cli_app.command()
132-
def link():
133-
"""
134-
Manually link proxies from directory to
135-
source media in open DaVinci Resolve project
136-
"""
137-
138-
# Init
139-
from ..settings.manager import SettingsManager
140-
from .utils.core import setup_rich_logging
141-
142-
settings = SettingsManager()
143-
144-
setup_rich_logging()
145-
logger = logging.getLogger(__name__)
146-
logger.setLevel(settings["app"]["loglevel"])
147-
# End init
148-
149-
from ..queuer import link
150-
151-
print("\n")
152-
console.rule(f"[green bold]Link proxies[/] :link:", align="left")
153-
print("\n")
154-
155-
link.main()
156-
157-
158131
@cli_app.command()
159132
def work(
160133
hide_banner: bool = hide_banner,
@@ -181,7 +154,7 @@ def work(
181154

182155
print("\n")
183156

184-
from ..worker import launch_workers
157+
from proxima.worker import launch_workers
185158

186159
launch_workers.main(workers_to_launch)
187160

@@ -239,7 +212,7 @@ def celery(
239212
def config():
240213
"""Open user settings configuration file for editing"""
241214

242-
from ..settings.manager import SettingsManager
215+
from proxima.settings import SettingsManager
243216

244217
settings = SettingsManager()
245218

@@ -249,6 +222,8 @@ def config():
249222
)
250223
print("\n")
251224

225+
# TODO: Cross platform alternative to this hack?
226+
# labels: enhancement
252227
webbrowser.open_new(settings.user_file)
253228

254229

proxima/app/exceptions.py

+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
class ResolveUnsupportedPlatform(Exception):
2+
"""
3+
Exception raised when Resolve's API does not support the current system.
4+
5+
This will occur when using any OS other than Windows, MacOS or Linux.
6+
"""
7+
8+
def __init__(self, message: str = "Platform is not 'Windows', 'MacOS' or 'Linux'"):
9+
self.message = message
10+
super().__init__(self.message)
11+
12+
13+
class ResolveAPIConnectionError(Exception):
14+
"""
15+
Exception raised when Resolve API is unreachable.
16+
17+
This can occur when Resolve is not open or the API server has crashed.
18+
"""
19+
20+
def __init__(
21+
self,
22+
message: str = "Resolve Python API is not accessible. Is DaVinci Resolve running?",
23+
):
24+
self.message = message
25+
super().__init__(self.message)
26+
27+
28+
class ResolveNoCurrentProjectError(Exception):
29+
"""
30+
Exception raised when the current project is unknown.
31+
32+
This can occur when Resolve is running but no project has been opened.
33+
"""
34+
35+
def __init__(
36+
self,
37+
message: str = "Couldn't get the current project. Is a project open in Resolve?",
38+
):
39+
self.message = message
40+
super().__init__(self.message)
41+
42+
43+
class ResolveNoCurrentTimelineError(Exception):
44+
"""
45+
Exception raised when the current timeline is unknown.
46+
47+
This can occur when Resolve is running and a project is open
48+
but no timeline has been opened.
49+
"""
50+
51+
def __init__(
52+
self,
53+
message: str = "Couldn't get the current timeline. Is a timeline open in Resolve?",
54+
):
55+
self.message = message
56+
super().__init__(self.message)
57+
58+
59+
class ResolveNoMediaPoolError(Exception):
60+
"""
61+
Exception raised when the Media Pool object is not accessible.
62+
63+
This shouldn't occur under normal circumstances.
64+
"""
65+
66+
def __init__(self, message: str = "Resolve's Media Pool is inacessible."):
67+
self.message = message
68+
super().__init__(self.message)
69+
70+
71+
class ResolveLinkMismatchError(Exception):
72+
"""
73+
Exception raised when Resolve's `LinkProxyMedia` API method rejects a provided proxy.
74+
75+
The API method returns a bool with no additional error context.
76+
Common reasons for a mismatch include incorrect proxy settings (framerate, timecode)
77+
or a corrupt/unfinished proxy file.
78+
"""
79+
80+
def __init__(self, proxy_file: str, message: str = ""):
81+
82+
self.proxy_file = proxy_file
83+
84+
if message != "":
85+
self.message = message
86+
else:
87+
self.message = f"Couldn't link source media to proxy '{self.proxy_file}'\n"
88+
f"The proxy file may be corrupt, incomplete or encoding settings may be incorrect (framerate, timecode, etc)"
89+
90+
super().__init__(self.message)
91+
92+
93+
class ResolveLostMPIReferenceError(Exception):
94+
"""
95+
Exception raised when media pool items passed to Resolve's `LinkProxyMedia` no longer reference in memory objects.
96+
97+
This occurs when any amount of project switching occurs between queuing and linking.
98+
MPI references exist per session and will be lost on project changes.
99+
"""
100+
101+
def __init__(self, media_pool_item, message: str = ""):
102+
103+
self.media_pool_item = media_pool_item
104+
105+
if message != "":
106+
self.message = message
107+
else:
108+
self.message = f"Lost reference to original media pool items, has the project been changed?\n"
109+
f"Post-encode linking after a project change is not possible without re-iterating media pool items."
110+
111+
super().__init__(self.message)
112+
113+
114+
class NoneLinkableError(Exception):
115+
"""
116+
Exception raised when no jobs passed to `ProxyLinker` are in the `linkable_types` list
117+
118+
This shouldn't occur under normal circumstances, but may occur if the `linkable_types`
119+
list is changed in a way that no queued proxies will match the list.
120+
"""
121+
122+
def __init__(self, message: str = ""):
123+
124+
if message != "":
125+
self.message = message
126+
else:
127+
self.message = (
128+
f"No jobs passed to ProxyLinker are in the 'linkable_types' list.\n"
129+
)
130+
"This shouldn't happen under normal circumstances. Probably the list has been misconfigured."
131+
132+
super().__init__(self.message)

proxima/queuer/handlers.py

+13-9
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import os
44
import pathlib
55
import shutil
6-
from typing import Union
6+
from typing import Union, Tuple
77

88
from rich import print as pprint
99
from rich.prompt import Confirm, Prompt
@@ -174,7 +174,7 @@ def handle_orphaned_proxies(media_list: list) -> list:
174174

175175

176176
def handle_already_linked(
177-
media_list: list, unlinked_types: list = ["Offline", "None"]
177+
media_list: list, unlinked_types: Tuple[str, ...] = ("Offline", "None")
178178
) -> list:
179179
"""Remove items from media-list that are already linked to a proxy.
180180
@@ -205,7 +205,7 @@ def handle_already_linked(
205205

206206

207207
def handle_existing_unlinked(
208-
media_list: list, unlinked_types: list = ["Offline", "None"]
208+
media_list: list, unlinked_types: Tuple[str, ...] = ("Offline", "None")
209209
) -> list:
210210

211211
"""Prompts user to either link or re-render unlinked proxy media that exists in the expected location.
@@ -320,12 +320,16 @@ def get_newest_proxy_file(media, expected_proxy_path: str) -> Union[str, None]:
320320
linkable_now.append(x)
321321
media_list.remove(x)
322322

323-
remaining = link.link_proxies_with_mpi(
324-
linkable_now,
325-
linkable_types=["Offline", "None"],
326-
prompt_rerender=True,
327-
)
328-
media_list.extend(remaining)
323+
proxy_linker = link.ProxyLinker(linkable_now)
324+
proxy_linker.link()
325+
326+
# Prompt to requeue if any failures
327+
if proxy_linker.mismatch_fail:
328+
if Confirm.ask(
329+
f"[yellow]{len(proxy_linker.mismatch_fail)} files failed to link."
330+
"They may be corrupt or incomplete. Re-render them?"
331+
):
332+
media_list.extend(proxy_linker.mismatch_fail)
329333

330334
else:
331335

0 commit comments

Comments
 (0)