300 lines
10 KiB
Python
300 lines
10 KiB
Python
from io import StringIO
|
|
from time import sleep
|
|
from datetime import datetime
|
|
import streamlit as st
|
|
from bs4 import BeautifulSoup
|
|
|
|
|
|
def find_start_seconds(region):
|
|
"""Returns start second for region."""
|
|
if len(region["Start"]) > 9:
|
|
start_time = datetime.strptime(region["Start"], "%H:%M:%S.%f")
|
|
elif len(region["Start"]) in [8, 9]:
|
|
start_time = datetime.strptime(region["Start"], "%M:%S.%f")
|
|
elif len(region["Start"]) < 8:
|
|
start_time = datetime.strptime(region["Start"], "%S.%f")
|
|
|
|
t = start_time.time()
|
|
return (t.hour * 60 + t.minute) * 60 + t.second
|
|
|
|
|
|
def find_lenght_seconds(region):
|
|
"""Returns lenght of region in seconds."""
|
|
if len(region["Length"]) > 9:
|
|
lenght = datetime.strptime(region["Length"], "%H:%M:%S.%f")
|
|
elif len(region["Length"]) in [8, 9]:
|
|
lenght = datetime.strptime(region["Length"], "%M:%S.%f")
|
|
elif len(region["Length"]) < 8:
|
|
lenght = datetime.strptime(region["Length"], "%S.%f")
|
|
t = lenght.time()
|
|
return (t.hour * 60 + t.minute) * 60 + t.second
|
|
|
|
|
|
def colorize(soup):
|
|
# Get colors from clips on timeline.
|
|
colors = {}
|
|
timeline = soup.find("Tracks")
|
|
for region in timeline.find_all("Region"):
|
|
try:
|
|
colors[str(region["Ref"])] = int(region["Colour"])
|
|
except:
|
|
pass
|
|
|
|
soup = colorize_clipboard(soup, colors)
|
|
soup = colorize_timeline(soup, colors)
|
|
return soup
|
|
|
|
|
|
def colorize_clipboard(soup, colors):
|
|
"""Give colors to clips in clipboard."""
|
|
|
|
clipboard = soup.find("Clipboard")
|
|
for group in clipboard.find_all("Group"):
|
|
# new_group_tag = soup.new_tag('Group', {'Caption':group['Caption'], 'IsExpanded':group['IsExpanded']})
|
|
new_group_tag = group
|
|
# Go over clips (nested regions).
|
|
for clip in group.find_all("Clip"):
|
|
# new_clip_tag = soup.new_tag('Clip', {'Name':clip['Name'], 'Start':clip['Start'], 'Length':clip['Length']})
|
|
new_clip_tag = clip
|
|
for region in clip.find_all("Region"):
|
|
new_region_tag = region
|
|
try:
|
|
new_region_tag["Colour"] = colors[region["Ref"]]
|
|
except KeyError:
|
|
new_region_tag["Colour"] = "0"
|
|
new_clip_tag.append(new_region_tag)
|
|
new_group_tag.append(new_clip_tag)
|
|
|
|
# Go over regions.
|
|
for region in group.find_all("Region"):
|
|
if region.parent.name == "Clip":
|
|
continue
|
|
new_region_tag = region
|
|
try:
|
|
new_region_tag["Colour"] = colors[region["Ref"]]
|
|
except KeyError:
|
|
new_region_tag["Colour"] = "0"
|
|
new_group_tag.append(new_region_tag)
|
|
new_clipboard_tag.append(new_group_tag)
|
|
|
|
soup.Session.Clipboard.replace_with(new_clipboard_tag)
|
|
|
|
return soup
|
|
|
|
|
|
def colorize_timeline(soup, colors):
|
|
print(colors)
|
|
# Give colors to clips on timeline.
|
|
new_tracks_tag = soup.find("Tracks")
|
|
tracks = soup.find("Tracks")
|
|
|
|
for track in tracks.find_all("Track"):
|
|
new_track_tag = track
|
|
for region in track.find_all("Region"):
|
|
new_region_tag = region
|
|
try:
|
|
new_region_tag["Colour"] = colors[region["Ref"]]
|
|
except KeyError:
|
|
print(region)
|
|
new_region_tag["Colour"] = "0"
|
|
new_track_tag.append(new_region_tag)
|
|
new_tracks_tag.append(new_track_tag)
|
|
soup.Session.Tracks.replace_with(new_tracks_tag)
|
|
return soup
|
|
|
|
|
|
def print_analyzing(soup):
|
|
regions = soup.find_all("Region")
|
|
n_regions = len(regions)
|
|
for region in regions:
|
|
try:
|
|
with s0:
|
|
st.markdown(f':green[{region["Name"]}]')
|
|
sleep(1 / (n_regions * 2))
|
|
except KeyError:
|
|
pass
|
|
|
|
|
|
def colorize_groups(soup):
|
|
"""Give color to regions based on groups they are in."""
|
|
groups = soup.find_all("Group")
|
|
ids = []
|
|
ids_groups = []
|
|
for group in reversed(groups):
|
|
print(group["Caption"])
|
|
regions = []
|
|
# try:
|
|
for region in group.find_all("Region"):
|
|
if region["Ref"] not in ids: # Exclude if already in another group.
|
|
regions.append(region["Ref"])
|
|
# except:
|
|
# pass
|
|
ids += regions
|
|
ids_groups.append(regions)
|
|
|
|
colors = {}
|
|
for group in ids_groups:
|
|
group_color = (ids_groups.index(group) * 50) % 360
|
|
for ref in group:
|
|
colors[ref] = group_color
|
|
|
|
soup = colorize_clipboard(soup, colors)
|
|
soup = colorize_timeline(soup, colors)
|
|
return soup
|
|
|
|
|
|
def make_rainbow(soup):
|
|
"""Returns a tracks tag colored like a rainbow."""
|
|
|
|
new_tracks_tag = soup.find("Tracks")
|
|
tracks = soup.find("Tracks")
|
|
regions = tracks.find_all("Region")
|
|
min_start = min([find_start_seconds(i) for i in regions])
|
|
max_end = max(
|
|
[int(find_start_seconds(i) + find_lenght_seconds(i)) for i in regions]
|
|
)
|
|
|
|
lenght = int(max_end - min_start)
|
|
|
|
# Give colors to clips on timeline.
|
|
new_tracks_tag = soup.find("Tracks")
|
|
tracks = soup.find("Tracks")
|
|
for track in tracks.find_all("Track"):
|
|
new_track_tag = track
|
|
for region in track.find_all("Region"):
|
|
new_region_tag = region
|
|
try:
|
|
middle = find_start_seconds(region) + find_lenght_seconds(region) / 2
|
|
new_region_tag["Colour"] = int(((middle) / lenght) * 360)
|
|
except KeyError:
|
|
new_region_tag["Colour"] = "0"
|
|
new_track_tag.append(new_region_tag)
|
|
new_tracks_tag.append(new_track_tag)
|
|
|
|
soup.Session.Tracks.replace_with(new_tracks_tag)
|
|
return soup
|
|
|
|
st.title(":green[Colorize] your :red[Hindenburg] project")
|
|
|
|
t1, t2, t3 = st.tabs(['From timeline', 'From clipboard', 'As rainbow 🌈'])
|
|
with t1:
|
|
st.markdown(
|
|
"""
|
|
1. Set colors for a few clips on your timeline, ideally at least one clip from
|
|
each recording.
|
|
2. Upload your Hindenburg project file (ending with *.nhsx*) below to add the same color
|
|
to other clips originating from the same recording.
|
|
3. Choose *Colors from timeline* to download the project file **and put it in the same folder as your original project file**.
|
|
"""
|
|
)
|
|
|
|
with st.expander(label="Unclear? See an example here.", expanded=False):
|
|
st.write("*Put colors on clips like this...*")
|
|
st.image("before.png")
|
|
st.write("*...and you get a file that looks like this*")
|
|
st.image("after.png")
|
|
|
|
with t2:
|
|
st.markdown('''
|
|
This alternative can be good if you have created clipboards for each person/interview as all clips in the same clipboard will
|
|
be given the same color.
|
|
If you have clips originating from the same recording in different clipboards it will be given the
|
|
color for the last clipbord where it is included.
|
|
Choose *Colors from clipboard* to download the project file **and put it in the same folder as your original project file**.
|
|
''')
|
|
|
|
with t3:
|
|
st.markdown('''
|
|
This alternative makes your timeline into a beautiful rainbow! Lots of love but maybe not where you will start a longer project.
|
|
Choose *Download rainbow project!* to download the project file **and put it in the same folder as your original project file**.
|
|
''')
|
|
st.markdown('No data is saved anywhere. Made by [Lasse Edfast](https://lasseedfast.se). You need to be using Hindenburg 2.0 to use colors.')
|
|
# Ask for file.
|
|
uploaded_file = st.file_uploader(
|
|
"*Upload your project file*",
|
|
label_visibility="hidden",
|
|
type="nhsx",
|
|
accept_multiple_files=False,
|
|
)
|
|
|
|
if uploaded_file:
|
|
if "soup" not in st.session_state: # Make soup.
|
|
stringio = StringIO(uploaded_file.getvalue().decode("utf-8"))
|
|
soup = BeautifulSoup(stringio.read(), "xml")
|
|
st.session_state["soup"] = soup
|
|
soup = st.session_state["soup"]
|
|
|
|
project_name = soup.find("AudioPool")["Path"].replace(" Files", "")
|
|
new_clipboard_tag = soup.new_tag("Clipboard")
|
|
|
|
s0 = st.empty()
|
|
with s0:
|
|
if 'analyzed' not in st.session_state:
|
|
print_analyzing(soup)
|
|
sleep(1)
|
|
|
|
st.session_state['analyzed'] = True
|
|
st.markdown("Choose your colors:")
|
|
sleep(0.3)
|
|
# Area to print things.
|
|
c1, c2, c3 = st.columns(3)
|
|
|
|
with c1:
|
|
if "soup1" not in st.session_state:
|
|
soup1 = colorize(soup)
|
|
st.session_state["soup1"] = soup1
|
|
soup1 = st.session_state["soup1"]
|
|
|
|
# Allow the user to download file.
|
|
st.download_button(
|
|
"🎨 Colors from timeline",
|
|
soup1.encode("utf-8"),
|
|
file_name=f"{project_name}_colors.nhsx",
|
|
)
|
|
|
|
with c2:
|
|
if "soup2" not in st.session_state:
|
|
soup2 = colorize_groups(soup)
|
|
st.session_state["soup2"] = soup2
|
|
soup2 = st.session_state["soup2"]
|
|
st.download_button(
|
|
"🎨 Colors from clipboard",
|
|
soup2.encode("utf-8"),
|
|
file_name=f"{project_name}_colors.nhsx",
|
|
)
|
|
with c3:
|
|
if "soup3" not in st.session_state:
|
|
soup3 = make_rainbow(soup)
|
|
st.session_state["soup3"] = soup3
|
|
else:
|
|
soup3 = st.session_state["soup3"]
|
|
|
|
s3 = st.empty()
|
|
with s3:
|
|
rainbow = st.button("🌈 Rainbow timeline!")
|
|
|
|
if rainbow:
|
|
for _ in range(0, 4):
|
|
with s3:
|
|
st.markdown("🌧️")
|
|
sleep(0.4)
|
|
with s3:
|
|
st.markdown("")
|
|
sleep(0.15)
|
|
with s3:
|
|
st.markdown("🌦️")
|
|
sleep(1)
|
|
with s3:
|
|
rainbows = []
|
|
for _ in range(0, 10):
|
|
rainbows.append("🌈")
|
|
st.markdown("".join(rainbows))
|
|
sleep(0.09)
|
|
|
|
with s3:
|
|
st.download_button(
|
|
"🌈 Download rainbow project! 🌈",
|
|
soup3.encode("utf-8"),
|
|
file_name=f"{project_name}_rainbow.nhsx",
|
|
)
|