@danhachuel/thunderbolt 0.4.88 → 0.4.90
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/main.py +35 -37
- package/hermes_ui/automation_worker.py +6 -4
- package/hermes_ui/pipeline_worker.py +6 -4
- package/package.json +1 -1
- package/sitecustomize.py +10 -1
package/app/main.py
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import sys
|
|
3
|
+
import io
|
|
3
4
|
|
|
4
5
|
os.environ["PYTHONIOENCODING"] = "utf-8"
|
|
5
6
|
os.environ["PYTHONUTF8"] = "1"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
|
|
8
|
+
def _force_utf8_stream(stream: object) -> object:
|
|
9
|
+
buffer = getattr(stream, "buffer", None)
|
|
10
|
+
encoding = str(getattr(stream, "encoding", "") or "").lower().replace("-", "_")
|
|
11
|
+
if buffer is not None and encoding != "utf_8":
|
|
12
|
+
return io.TextIOWrapper(buffer, encoding="utf-8", errors="replace", line_buffering=True)
|
|
13
|
+
return stream
|
|
14
|
+
|
|
15
|
+
_original_stdout, _original_stderr = sys.stdout, sys.stderr
|
|
16
|
+
sys.stdout = _force_utf8_stream(sys.stdout)
|
|
17
|
+
sys.stderr = _force_utf8_stream(sys.stderr)
|
|
10
18
|
|
|
11
19
|
import hashlib
|
|
12
20
|
import json
|
|
@@ -1303,7 +1311,6 @@ def render_channel_edit_form(channel: dict, youtube_account_ids: list[str], yout
|
|
|
1303
1311
|
edited_language = st.selectbox("Idioma do roteiro", language_options, index=language_options.index(normalize_video_language(channel.get("language") or "pt")) if normalize_video_language(channel.get("language") or "pt") in language_options else 0, format_func=video_language_label)
|
|
1304
1312
|
edited_style = st.selectbox("Fonte do vídeo", WIDE_STYLE_OPTIONS, index=WIDE_STYLE_OPTIONS.index(channel_video_source_value(channel.get("style_wide"))) if channel_video_source_value(channel.get("style_wide")) in WIDE_STYLE_OPTIONS else 0)
|
|
1305
1313
|
edited_aspect = st.selectbox("Proporção do vídeo", CHANNEL_ASPECT_RATIO_OPTIONS, index=CHANNEL_ASPECT_RATIO_OPTIONS.index(str(channel.get("video_aspect_ratio") or "Landscape 16:9")) if str(channel.get("video_aspect_ratio") or "Landscape 16:9") in CHANNEL_ASPECT_RATIO_OPTIONS else 0)
|
|
1306
|
-
edited_format = st.selectbox("Formato", CHANNEL_FORMAT_OPTIONS, index=CHANNEL_FORMAT_OPTIONS.index(str(channel.get("format") or "wide")) if str(channel.get("format") or "wide") in CHANNEL_FORMAT_OPTIONS else 0)
|
|
1307
1314
|
edited_niche = st.text_input("Nicho", value=str(channel.get("niche") or channel_niche_label(channel) if channel_niche_label(channel) != "SEM NICHO CONFIGURADO" else "") )
|
|
1308
1315
|
with edit_cols[1]:
|
|
1309
1316
|
edited_blueprint = st.selectbox("Blueprint Padrão", blueprint_ids, index=blueprint_ids.index(current_blueprint) if current_blueprint in blueprint_ids else 0, format_func=lambda item: blueprint_labels.get(item, item or "Sem Blueprint padrão"))
|
|
@@ -1325,7 +1332,7 @@ def render_channel_edit_form(channel: dict, youtube_account_ids: list[str], yout
|
|
|
1325
1332
|
else:
|
|
1326
1333
|
update_channel(channel_id, {
|
|
1327
1334
|
"name": edited_name.strip(), "url": edited_url.strip(), "handle": edited_handle.strip(), "language": edited_language,
|
|
1328
|
-
"style_wide": channel_video_source_storage(edited_style), "video_aspect_ratio": edited_aspect,
|
|
1335
|
+
"style_wide": channel_video_source_storage(edited_style), "video_aspect_ratio": edited_aspect,
|
|
1329
1336
|
"niche": edited_niche.strip(), "reference_channels": [item.strip() for item in re.split(r"[,|]", edited_niche) if item.strip()],
|
|
1330
1337
|
"default_blueprint_id": edited_blueprint.strip(), "blueprint_id": edited_blueprint.strip(),
|
|
1331
1338
|
"default_voice": edited_voice.strip(), "voice": edited_voice.strip(),
|
|
@@ -1996,7 +2003,6 @@ def render_tiktok_channels():
|
|
|
1996
2003
|
language = st.selectbox("Idioma do roteiro", VIDEO_LANGUAGE_SELECTION_OPTIONS, format_func=video_language_label, key="tiktok_import_language")
|
|
1997
2004
|
source_value = st.selectbox("Fonte do vídeo", WIDE_STYLE_OPTIONS, key="tiktok_import_source")
|
|
1998
2005
|
aspect_value = st.selectbox("Proporção do vídeo", CHANNEL_ASPECT_RATIO_OPTIONS, index=1, key="tiktok_import_aspect")
|
|
1999
|
-
format_value = st.selectbox("Formato", CHANNEL_FORMAT_OPTIONS, index=1, key="tiktok_import_format")
|
|
2000
2006
|
niche = st.text_input("Nicho", key="tiktok_import_niche")
|
|
2001
2007
|
prompt = st.selectbox("Prompt Master padrão", prompt_ids, format_func=lambda item: prompt_labels.get(item, item), key="tiktok_import_prompt")
|
|
2002
2008
|
voice = st.selectbox("Narrador/Voz Padrão", voice_options, format_func=lambda item: item or "Sem voz padrão", key="tiktok_import_voice")
|
|
@@ -2007,7 +2013,7 @@ def render_tiktok_channels():
|
|
|
2007
2013
|
if not valid_hhmm(automation_time):
|
|
2008
2014
|
st.error("O horário diário deve estar no formato HH:MM.")
|
|
2009
2015
|
st.stop()
|
|
2010
|
-
channel = create_channel(name.strip(), url.strip(), {"platform": "tiktok", "handle": handle.strip(), "language": language, "niche": niche.strip(), "description": description.strip(), "default_prompt_master": prompt, "prompt_master": prompt, "default_voice": voice, "voice": voice, "style_wide": channel_video_source_storage(source_value), "video_aspect_ratio": aspect_value, "format":
|
|
2016
|
+
channel = create_channel(name.strip(), url.strip(), {"platform": "tiktok", "handle": handle.strip(), "language": language, "niche": niche.strip(), "description": description.strip(), "default_prompt_master": prompt, "prompt_master": prompt, "default_voice": voice, "voice": voice, "style_wide": channel_video_source_storage(source_value), "video_aspect_ratio": aspect_value, "format": "Shorts", "automation_on": automation_on, "automation_time": automation_time.strip(), "metrics_source": "tiktok_public_page", "subscriber_count": imported.get("subscriber_count"), "video_count": imported.get("video_count")})
|
|
2011
2017
|
avatar_url = _tiktok_avatar_url(imported)
|
|
2012
2018
|
update_channel(channel["id"], {"platform": "tiktok", "default_prompt_master": prompt, "prompt_master": prompt, "avatar_url": avatar_url, "thumbnail_url": avatar_url})
|
|
2013
2019
|
st.session_state.pop("tiktok_channel_import", None)
|
|
@@ -2049,27 +2055,20 @@ def render_tiktok_channels():
|
|
|
2049
2055
|
if st.button("Apagar card", key=f"tiktok_import_card_delete_{channel_id}"):
|
|
2050
2056
|
st.session_state[f"tiktok_delete_{channel_id}"] = True
|
|
2051
2057
|
st.rerun()
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
st.rerun()
|
|
2067
|
-
st.caption(f"Fonte do vídeo: {channel_video_source_value(channel.get('style_wide'))}")
|
|
2068
|
-
niche_action = st.columns([1.0, 4.0])
|
|
2069
|
-
with niche_action[0]:
|
|
2070
|
-
if st.button("Editar nicho", key=f"tiktok_import_card_niche_{channel_id}"):
|
|
2071
|
-
st.session_state[f"tiktok_niche_{channel_id}"] = not st.session_state.get(f"tiktok_niche_{channel_id}", False)
|
|
2072
|
-
st.rerun()
|
|
2058
|
+
summary_cols = st.columns(4, gap="small")
|
|
2059
|
+
with summary_cols[0]:
|
|
2060
|
+
st.markdown("**Blueprint Padrão**")
|
|
2061
|
+
st.caption(prompt_labels.get(str(channel.get("default_prompt_master") or channel.get("prompt_master") or ""), "Sem Blueprint padrão"))
|
|
2062
|
+
with summary_cols[1]:
|
|
2063
|
+
st.markdown("**Nicho**")
|
|
2064
|
+
st.caption(channel_niche_label(channel))
|
|
2065
|
+
with summary_cols[2]:
|
|
2066
|
+
st.markdown("**Narrador/Voz Padrão**")
|
|
2067
|
+
st.caption(str(channel.get("default_voice") or channel.get("voice") or "Sem voz padrão"))
|
|
2068
|
+
with summary_cols[3]:
|
|
2069
|
+
st.markdown("**Idioma**")
|
|
2070
|
+
st.caption(video_language_label(normalize_video_language(channel.get("language") or "pt")))
|
|
2071
|
+
st.caption(f"Fonte do vídeo: {channel_video_source_value(channel.get('style_wide'))} · Proporção: {channel.get('video_aspect_ratio') or 'Portrait 9:16'}")
|
|
2073
2072
|
if st.session_state.get(f"tiktok_delete_{channel_id}"):
|
|
2074
2073
|
st.warning("Apagar este canal TikTok e os dados locais associados?")
|
|
2075
2074
|
confirm_cols = st.columns(2)
|
|
@@ -2097,7 +2096,6 @@ def render_tiktok_channels():
|
|
|
2097
2096
|
edit_language = st.selectbox("Idioma do roteiro", VIDEO_LANGUAGE_SELECTION_OPTIONS, index=VIDEO_LANGUAGE_SELECTION_OPTIONS.index(normalize_video_language(channel.get("language") or "pt")) if normalize_video_language(channel.get("language") or "pt") in VIDEO_LANGUAGE_SELECTION_OPTIONS else 0, format_func=video_language_label)
|
|
2098
2097
|
edit_source = st.selectbox("Fonte do vídeo", WIDE_STYLE_OPTIONS, index=WIDE_STYLE_OPTIONS.index(channel_video_source_value(channel.get("style_wide"))) if channel_video_source_value(channel.get("style_wide")) in WIDE_STYLE_OPTIONS else 0)
|
|
2099
2098
|
edit_aspect = st.selectbox("Proporção do vídeo", CHANNEL_ASPECT_RATIO_OPTIONS, index=CHANNEL_ASPECT_RATIO_OPTIONS.index(str(channel.get("video_aspect_ratio") or "Portrait 9:16")) if str(channel.get("video_aspect_ratio") or "Portrait 9:16") in CHANNEL_ASPECT_RATIO_OPTIONS else 1)
|
|
2100
|
-
edit_format = st.selectbox("Formato", CHANNEL_FORMAT_OPTIONS, index=CHANNEL_FORMAT_OPTIONS.index(str(channel.get("format") or "Shorts")) if str(channel.get("format") or "Shorts") in CHANNEL_FORMAT_OPTIONS else 1)
|
|
2101
2099
|
edit_voice = st.selectbox("Narrador/Voz Padrão", voice_options, index=voice_options.index(str(channel.get("default_voice") or channel.get("voice") or "")) if str(channel.get("default_voice") or channel.get("voice") or "") in voice_options else 0, format_func=lambda item: item or "Sem voz padrão")
|
|
2102
2100
|
edit_automation = st.toggle("Automação ON", value=bool(channel.get("automation_on", False)), key=f"tiktok_edit_automation_{channel_id}")
|
|
2103
2101
|
edit_time = st.text_input("Horário diário (HH:MM)", value=str(channel.get("automation_time") or "00:00"))
|
|
@@ -2107,7 +2105,7 @@ def render_tiktok_channels():
|
|
|
2107
2105
|
if not valid_hhmm(edit_time):
|
|
2108
2106
|
st.error("O horário diário deve estar no formato HH:MM.")
|
|
2109
2107
|
else:
|
|
2110
|
-
update_channel(channel_id, {"name": edit_name.strip(), "url": edit_url.strip(), "handle": edit_handle.strip(), "language": edit_language, "default_voice": edit_voice, "voice": edit_voice, "style_wide": channel_video_source_storage(edit_source), "video_aspect_ratio": edit_aspect, "
|
|
2108
|
+
update_channel(channel_id, {"name": edit_name.strip(), "url": edit_url.strip(), "handle": edit_handle.strip(), "language": edit_language, "default_voice": edit_voice, "voice": edit_voice, "style_wide": channel_video_source_storage(edit_source), "video_aspect_ratio": edit_aspect, "automation_on": edit_automation, "automation_time": edit_time.strip(), "description": edit_description.strip(), "platform": "tiktok"})
|
|
2111
2109
|
st.session_state.pop(f"tiktok_edit_{channel_id}", None)
|
|
2112
2110
|
st.rerun()
|
|
2113
2111
|
with st.expander("Últimos 10 vídeos publicados", expanded=False):
|
|
@@ -2164,7 +2162,6 @@ def render_tiktok_channels():
|
|
|
2164
2162
|
language = st.selectbox("Idioma do roteiro", VIDEO_LANGUAGE_SELECTION_OPTIONS, format_func=video_language_label, key="tiktok_manual_channel_language")
|
|
2165
2163
|
source_value = st.selectbox("Fonte do vídeo", WIDE_STYLE_OPTIONS, key="tiktok_manual_channel_source")
|
|
2166
2164
|
aspect_value = st.selectbox("Proporção do vídeo", CHANNEL_ASPECT_RATIO_OPTIONS, index=1, key="tiktok_manual_channel_aspect")
|
|
2167
|
-
format_value = st.selectbox("Formato", CHANNEL_FORMAT_OPTIONS, index=1, key="tiktok_manual_channel_format")
|
|
2168
2165
|
niche = st.text_input("Nicho", key="tiktok_manual_channel_niche")
|
|
2169
2166
|
prompt = st.selectbox("Prompt Master padrão", prompt_ids, format_func=lambda item: prompt_labels.get(item, item), key="tiktok_manual_channel_prompt")
|
|
2170
2167
|
voice = st.selectbox("Narrador/Voz Padrão", voice_options, format_func=lambda item: item or "Sem voz padrão", key="tiktok_manual_channel_voice")
|
|
@@ -2256,7 +2253,6 @@ def render_channels():
|
|
|
2256
2253
|
language = st.selectbox("Idioma do roteiro", VIDEO_LANGUAGE_SELECTION_OPTIONS, index=VIDEO_LANGUAGE_SELECTION_OPTIONS.index(normalize_video_language(imported.get("language") or "pt")) if normalize_video_language(imported.get("language") or "pt") in VIDEO_LANGUAGE_SELECTION_OPTIONS else 0, format_func=video_language_label, key="yt_import_language")
|
|
2257
2254
|
style = st.selectbox("Fonte do vídeo", WIDE_STYLE_OPTIONS, index=0, key="yt_import_style")
|
|
2258
2255
|
video_aspect_ratio = st.selectbox("Proporção do vídeo", CHANNEL_ASPECT_RATIO_OPTIONS, key="yt_import_aspect_ratio")
|
|
2259
|
-
video_format = st.selectbox("Formato", CHANNEL_FORMAT_OPTIONS, key="yt_import_format")
|
|
2260
2256
|
blueprint = st.selectbox("Blueprint Padrão", blueprint_ids, index=blueprint_ids.index(imported_blueprint) if imported_blueprint in blueprint_ids else 0, format_func=lambda item: blueprint_labels.get(item, item or "Sem Blueprint padrão"), key="yt_import_blueprint")
|
|
2261
2257
|
voice_options = voice_catalog(imported.get("default_voice") or imported.get("voice", ""))
|
|
2262
2258
|
current_voice = imported.get("default_voice") or imported.get("voice", "")
|
|
@@ -2286,7 +2282,7 @@ def render_channels():
|
|
|
2286
2282
|
"niche": niche.strip(),
|
|
2287
2283
|
"reference_channels": [item.strip() for item in re.split(r"[,|]", niche) if item.strip()],
|
|
2288
2284
|
"language": language,
|
|
2289
|
-
"style_wide": channel_video_source_storage(style), "video_aspect_ratio": video_aspect_ratio, "format":
|
|
2285
|
+
"style_wide": channel_video_source_storage(style), "video_aspect_ratio": video_aspect_ratio, "format": "wide",
|
|
2290
2286
|
"blueprint_id": blueprint.strip(),
|
|
2291
2287
|
"default_blueprint_id": blueprint.strip(),
|
|
2292
2288
|
"default_voice": voice.strip(),
|
|
@@ -5007,15 +5003,14 @@ def render_tiktok_automation():
|
|
|
5007
5003
|
with default_cols[3]:
|
|
5008
5004
|
prompt = st.selectbox("Prompt Master Padrão", prompt_ids, index=prompt_ids.index(channel.get("default_prompt_master", "")) if channel.get("default_prompt_master", "") in prompt_ids else 0, format_func=lambda item: prompt_labels.get(item, item), key=f"tiktok_automation_prompt_{channel_id}")
|
|
5009
5005
|
with default_cols[4]:
|
|
5010
|
-
st.
|
|
5011
|
-
st.caption("TikTok / Shorts")
|
|
5006
|
+
automation_format = st.selectbox("Formato", CHANNEL_FORMAT_OPTIONS, index=CHANNEL_FORMAT_OPTIONS.index(str(channel.get("format") or "Shorts")) if str(channel.get("format") or "Shorts") in CHANNEL_FORMAT_OPTIONS else 1, key=f"tiktok_automation_format_{channel_id}")
|
|
5012
5007
|
with default_cols[5]:
|
|
5013
5008
|
if st.button("Guardar", key=f"tiktok_automation_save_{channel_id}", use_container_width=True, type="primary"):
|
|
5014
5009
|
if not valid_hhmm(schedule_time):
|
|
5015
5010
|
st.error("Use o formato HH:MM, por exemplo 08:30.")
|
|
5016
5011
|
else:
|
|
5017
5012
|
avatar_url = _tiktok_avatar_url(channel)
|
|
5018
|
-
update_channel(channel_id, {"automation_on": bool(enabled), "automation_time": schedule_time.strip(), "default_prompt_master": prompt, "prompt_master": prompt, "platform": "tiktok", "video_aspect_ratio": "Portrait 9:16", "style_wide": "portrait", "avatar_url": avatar_url, "thumbnail_url": avatar_url})
|
|
5013
|
+
update_channel(channel_id, {"automation_on": bool(enabled), "automation_time": schedule_time.strip(), "default_prompt_master": prompt, "prompt_master": prompt, "platform": "tiktok", "format": automation_format, "video_aspect_ratio": "Portrait 9:16", "style_wide": "portrait", "avatar_url": avatar_url, "thumbnail_url": avatar_url})
|
|
5019
5014
|
st.success("Automação TikTok guardada.")
|
|
5020
5015
|
st.rerun()
|
|
5021
5016
|
st.divider()
|
|
@@ -5110,6 +5105,8 @@ def render_automation():
|
|
|
5110
5105
|
st.markdown("**Thumbnail Blueprint**")
|
|
5111
5106
|
st.caption(str(paired_thumbnail.get("name") or "Generic_Thumbnail_Blueprint"))
|
|
5112
5107
|
with default_cols[4]:
|
|
5108
|
+
automation_format = st.selectbox("Formato", CHANNEL_FORMAT_OPTIONS, index=CHANNEL_FORMAT_OPTIONS.index(str(channel.get("format") or "wide")) if str(channel.get("format") or "wide") in CHANNEL_FORMAT_OPTIONS else 0, key=f"automation_format_{channel_id}")
|
|
5109
|
+
with default_cols[5]:
|
|
5113
5110
|
automation_voice = st.selectbox(
|
|
5114
5111
|
"Narrador/Voz Padrão",
|
|
5115
5112
|
voice_options,
|
|
@@ -5125,6 +5122,7 @@ def render_automation():
|
|
|
5125
5122
|
update_channel(channel_id, {
|
|
5126
5123
|
"automation_on": bool(enabled),
|
|
5127
5124
|
"automation_time": schedule_time.strip(),
|
|
5125
|
+
"format": automation_format,
|
|
5128
5126
|
})
|
|
5129
5127
|
set_channel_defaults(channel_id, automation_blueprint, automation_voice)
|
|
5130
5128
|
paired_id = str(paired_thumbnail.get("id") or "Generic_Thumbnail_Blueprint")
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import sys
|
|
3
|
+
import io
|
|
3
4
|
|
|
4
5
|
os.environ["PYTHONIOENCODING"] = "utf-8"
|
|
5
6
|
os.environ["PYTHONUTF8"] = "1"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
_original_stdout, _original_stderr = sys.stdout, sys.stderr
|
|
8
|
+
if getattr(sys.stdout, "buffer", None) is not None and str(getattr(sys.stdout, "encoding", "") or "").lower().replace("-", "_") != "utf_8":
|
|
9
|
+
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace", line_buffering=True)
|
|
10
|
+
if getattr(sys.stderr, "buffer", None) is not None and str(getattr(sys.stderr, "encoding", "") or "").lower().replace("-", "_") != "utf_8":
|
|
11
|
+
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace", line_buffering=True)
|
|
10
12
|
|
|
11
13
|
import argparse
|
|
12
14
|
import time
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import sys
|
|
3
|
+
import io
|
|
3
4
|
|
|
4
5
|
os.environ["PYTHONIOENCODING"] = "utf-8"
|
|
5
6
|
os.environ["PYTHONUTF8"] = "1"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
_original_stdout, _original_stderr = sys.stdout, sys.stderr
|
|
8
|
+
if getattr(sys.stdout, "buffer", None) is not None and str(getattr(sys.stdout, "encoding", "") or "").lower().replace("-", "_") != "utf_8":
|
|
9
|
+
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace", line_buffering=True)
|
|
10
|
+
if getattr(sys.stderr, "buffer", None) is not None and str(getattr(sys.stderr, "encoding", "") or "").lower().replace("-", "_") != "utf_8":
|
|
11
|
+
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace", line_buffering=True)
|
|
10
12
|
|
|
11
13
|
import json
|
|
12
14
|
import queue
|
package/package.json
CHANGED
package/sitecustomize.py
CHANGED
|
@@ -3,13 +3,22 @@ from __future__ import annotations
|
|
|
3
3
|
|
|
4
4
|
import os
|
|
5
5
|
import sys
|
|
6
|
+
import io
|
|
6
7
|
|
|
7
8
|
os.environ["PYTHONIOENCODING"] = "utf-8"
|
|
8
9
|
os.environ["PYTHONUTF8"] = "1"
|
|
9
10
|
|
|
10
11
|
for stream_name in ("stdin", "stdout", "stderr"):
|
|
11
12
|
stream = getattr(sys, stream_name, None)
|
|
12
|
-
if stream is not None
|
|
13
|
+
buffer = getattr(stream, "buffer", None) if stream is not None else None
|
|
14
|
+
encoding = str(getattr(stream, "encoding", "") or "").lower().replace("-", "_") if stream is not None else ""
|
|
15
|
+
if buffer is not None and encoding != "utf_8":
|
|
16
|
+
try:
|
|
17
|
+
replacement = io.TextIOWrapper(buffer, encoding="utf-8", errors="replace", line_buffering=True)
|
|
18
|
+
setattr(sys, stream_name, replacement)
|
|
19
|
+
except (OSError, ValueError):
|
|
20
|
+
pass
|
|
21
|
+
elif stream is not None and hasattr(stream, "reconfigure"):
|
|
13
22
|
try:
|
|
14
23
|
stream.reconfigure(encoding="utf-8", errors="replace")
|
|
15
24
|
except (OSError, ValueError):
|