Skip to content

Commit

Permalink
Fixed probing and editing paths w/ weird encoding
Browse files Browse the repository at this point in the history
`probe_files()` now uses `encoding='utf-8'` and `errors='ignore'` while
reading/writing to match FFmpeg/FFprobe's output. The `.communicate()`
call is now done within the try-except block for extra safety. All probe
files `open()` calls now also specify `encoding='utf-8'` as well (but do
not ignore errors, so invalid probes can be replaced or examined).

Also, `Edit.ffmpeg()` now parses progress lines within its nearby
try-except block as well, defaulting to an empty list on errors to
avoid crashing when a line cannot be parsed.

For one last line of defense, `util.ffmpeg_async()` now also uses
`encoding='utf-8'` and `errors='ignore'` itself, which likely stops
the progress lines from ever erroring out in the first place.

This is probably a reason to start saving and reading probes in bytes
rather than text. Also, I couldn't get logging to work, even with
`encoding` and `errors` parameters. It just never works (which is
kinda fine actually since we're still compiling for 3.8, which
didn't have those parameters for logging yet).
  • Loading branch information
thisismy-github committed Mar 1, 2024
1 parent 4479999 commit 4b28d9e
Show file tree
Hide file tree
Showing 2 changed files with 17 additions and 10 deletions.
23 changes: 14 additions & 9 deletions main.pyw
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ def probe_files(*files: str, refresh: bool = False, write: bool = True) -> dict[
except: logging.warning('(!) FAILED TO DELETE UNWANTED PROBE FILE: ' + format_exc())
probe_exists = False
else:
with open(probe_file, 'r') as probe:
with open(probe_file, 'r', encoding='utf-8') as probe:
try:
probes[file] = parse_json(probe.read())
except:
Expand All @@ -292,6 +292,8 @@ def probe_files(*files: str, refresh: bool = False, write: bool = True) -> dict[
subprocess.Popen(
cmd,
text=True, # decodes stdout into text rather than a byte stream
encoding='utf-8', # use ffmpeg/ffprobe's encoding so `text=True` doesn't crash for paths w/ scary characters
errors='ignore', # drop bad characters when there's an encoding error (which won't matter for our usecase)
stdout=subprocess.PIPE, # don't use `shell=True` for the same reason as above
startupinfo=constants.STARTUPINFO
) # ^ hides the command prompt that appears w/o `shell=True`
Expand All @@ -301,12 +303,12 @@ def probe_files(*files: str, refresh: bool = False, write: bool = True) -> dict[
# for any files that did not have pre-existing probe files, wait until...
# ...their processes are complete and read output directly from the process
for file, probe_file, process in processes:
out, err = process.communicate()
try:
out, err = process.communicate() # NOTE: this is where errors happen on filenames with the wrong encoding above
probes[file] = parse_json(out)
if write: # manually write probe to file
with open(probe_file, 'w') as probe:
probe.write(out)
with open(probe_file, 'w', encoding='utf-8') as probe:
probe.write(out) # ^ DON'T use `errors='ignore'` here. if we somehow error out here, i'd rather know why
except:
logging.warning(f'(!) {file} could not be correctly parsed by FFprobe: {format_exc()}')
show_on_statusbar(f'{file} could not be correctly parsed by FFprobe.')
Expand Down Expand Up @@ -766,10 +768,13 @@ class Edit:
end_index = process.stdout.tell()
try:
process.stdout.seek(start_index, 0) # seeking back sometimes throws an error?
progress_lines = process.stdout.read(end_index - start_index).split('\n')
except OSError:
logging.warning(f'(!) Failed to seek backwards from index {end_index} to index {start_index} in FFmpeg\'s stdout pipe, retrying...')
continue
progress_lines = process.stdout.read(end_index - start_index).split('\n')
except:
logging.warning('(!) Unexpected error while seeking or reading from FFmpeg\'s stdout pipe: ' + format_exc())
progress_lines = []

# can't seek in streams on linux -> call & measure readline()'s delay until it buffers
# NOTE: this is WAY less efficient and updates noticably slower when sleeping for the same duration. too bad lol
Expand Down Expand Up @@ -2950,7 +2955,7 @@ class GUI_Instance(QtW.QMainWindow, Ui_MainWindow):
if probe_data is None: # VLC not finished, no data provided, but probe file is being generated
while not exists(probe_file):
sleep(0.01)
with open(probe_file, 'r') as probe:
with open(probe_file, 'r', encoding='utf-8') as probe:
while probe_data is None:
try:
probe_data = parse_json(probe.read())
Expand Down Expand Up @@ -3063,7 +3068,7 @@ class GUI_Instance(QtW.QMainWindow, Ui_MainWindow):
start = get_time()
while not exists(probe_file):
sleep(0.01)
with open(probe_file, 'r') as probe:
with open(probe_file, 'r', encoding='utf-8') as probe:
while probe_data is None:
try:
probe_data = parse_json(probe.read())
Expand Down Expand Up @@ -3269,7 +3274,7 @@ class GUI_Instance(QtW.QMainWindow, Ui_MainWindow):
probe_file = f'{constants.PROBE_DIR}{sep}{basename}_{stat.st_mtime}_{filesize}.txt'
probe_exists = exists(probe_file)
if probe_exists: # probe file already exists
with open(probe_file, 'r') as probe:
with open(probe_file, 'r', encoding='utf-8') as probe:
try:
probe_data = parse_json(probe.read())
probe_process = None
Expand Down Expand Up @@ -3330,7 +3335,7 @@ class GUI_Instance(QtW.QMainWindow, Ui_MainWindow):
sleep(0.02)

# attempt to parse probe file. if successful, this might be actual media
with open(probe_file, 'r') as probe:
with open(probe_file, 'r', encoding='utf-8') as probe:
while probe_data is None:
if probe.read(): # keep reading until the file actually contains data
sleep(0.1)
Expand Down
4 changes: 3 additions & 1 deletion util.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,9 @@ def ffmpeg_async(cmd: str, priority: int = None, niceness: int = None, threads:
startupinfo=constants.STARTUPINFO, # hides command prompt that appears w/o `shell=True`
creationflags=priority, # sets the priority level ffmpeg will start with
start_new_session=True, # this allows us to more easily kill the ffmpeg process if needed
text=True # turns stdout into easily parsible lines of text rather than a byte stream
text=True, # turns stdout into easily parsible lines of text rather than a byte stream
encoding='utf-8', # ffmpeg/ffprobe output text in utf-8 encoding
errors='ignore' # if there are encoding errors anyway, just drop the bad characters
)


Expand Down

0 comments on commit 4b28d9e

Please sign in to comment.