resolve: load sequence wip

This commit is contained in:
Jakub Jezek 2021-01-27 19:12:27 +01:00
parent be8f350f82
commit 5174eed16d
No known key found for this signature in database
GPG key ID: C4B96E101D2A47F3
6 changed files with 303 additions and 170 deletions

View file

@ -26,7 +26,7 @@ Using a script
--------------
DaVinci Resolve needs to be running for a script to be invoked.
For a Resolve script to be executed from an external folder, the script needs to know of the API location.
For a Resolve script to be executed from an external folder, the script needs to know of the API location.
You may need to set the these environment variables to allow for your Python installation to pick up the appropriate dependencies as shown below:
Mac OS X:
@ -47,9 +47,9 @@ You may need to set the these environment variables to allow for your Python ins
As with Fusion scripts, Resolve scripts can also be invoked via the menu and the Console.
On startup, DaVinci Resolve scans the subfolders in the directories shown below and enumerates the scripts found in the Workspace application menu under Scripts.
On startup, DaVinci Resolve scans the subfolders in the directories shown below and enumerates the scripts found in the Workspace application menu under Scripts.
Place your script under Utility to be listed in all pages, under Comp or Tool to be available in the Fusion page or under folders for individual pages (Edit, Color or Deliver). Scripts under Deliver are additionally listed under render jobs.
Placing your script here and invoking it from the menu is the easiest way to use scripts.
Placing your script here and invoking it from the menu is the easiest way to use scripts.
Mac OS X:
- All users: /Library/Application Support/Blackmagic Design/DaVinci Resolve/Fusion/Scripts
- Specific user: /Users/<UserName>/Library/Application Support/Blackmagic Design/DaVinci Resolve/Fusion/Scripts
@ -137,7 +137,7 @@ Project
StartRendering(jobId1, jobId2, ...) --> Bool # Starts rendering jobs indicated by the input job ids.
StartRendering([jobIds...], isInteractiveMode=False) --> Bool # Starts rendering jobs indicated by the input job ids.
# The optional "isInteractiveMode", when set, enables error feedback in the UI during rendering.
StartRendering(isInteractiveMode=False) --> Bool # Starts rendering all queued render jobs.
StartRendering(isInteractiveMode=False) --> Bool # Starts rendering all queued render jobs.
# The optional "isInteractiveMode", when set, enables error feedback in the UI during rendering.
StopRendering() --> None # Stops any current render processes.
IsRenderingInProgress() --> Bool # Returns True if rendering is in progress.
@ -245,7 +245,7 @@ MediaPoolItem
GetClipColor() --> string # Returns the item color as a string.
SetClipColor(colorName) --> Bool # Sets the item color based on the colorName (string).
ClearClipColor() --> Bool # Clears the item color.
GetClipProperty(propertyName=None) --> string|dict # Returns the property value for the key 'propertyName'.
GetClipProperty(propertyName=None) --> string|dict # Returns the property value for the key 'propertyName'.
# If no argument is specified, a dict of all clip properties is returned. Check the section below for more information.
SetClipProperty(propertyName, propertyValue) --> Bool # Sets the given property to propertyValue (string). Check the section below for more information.
LinkProxyMedia(propertyName) --> Bool # Links proxy media (absolute path) with the current clip.
@ -335,7 +335,7 @@ TimelineItem
DeleteMarkerByCustomData(customData) --> Bool # Delete first matching marker with specified customData.
AddFlag(color) --> Bool # Adds a flag with given color (string).
GetFlagList() --> [colors...] # Returns a list of flag colors assigned to the item.
ClearFlags(color) --> Bool # Clear flags of the specified color. An "All" argument is supported to clear all flags.
ClearFlags(color) --> Bool # Clear flags of the specified color. An "All" argument is supported to clear all flags.
GetClipColor() --> string # Returns the item color as a string.
SetClipColor(colorName) --> Bool # Sets the item color based on the colorName (string).
ClearClipColor() --> Bool # Clears the item color.
@ -378,7 +378,7 @@ Similarly the Lua API implements "dict" as a table with the dictionary key as fi
Looking up Project and Clip properties
--------------------------------------
This section covers additional notes for the functions "Project:GetSetting", "Project:SetSetting", "Timeline:GetSetting", "Timeline:SetSetting", "MediaPoolItem:GetClipProperty" and
This section covers additional notes for the functions "Project:GetSetting", "Project:SetSetting", "Timeline:GetSetting", "Timeline:SetSetting", "MediaPoolItem:GetClipProperty" and
"MediaPoolItem:SetClipProperty". These functions are used to get and set properties otherwise available to the user through the Project Settings and the Clip Attributes dialogs.
The functions follow a key-value pair format, where each property is identified by a key (the settingName or propertyName parameter) and possesses a value (typically a text value). Keys and values are
@ -387,13 +387,13 @@ designed to be easily correlated with parameter names and values in the Resolve
Some properties may be read only - these include intrinsic clip properties like date created or sample rate, and properties that can be disabled in specific application contexts (e.g. custom colorspaces
in an ACES workflow, or output sizing parameters when behavior is set to match timeline)
Getting values:
Invoke "Project:GetSetting", "Timeline:GetSetting" or "MediaPoolItem:GetClipProperty" with the appropriate property key. To get a snapshot of all queryable properties (keys and values), you can call
"Project:GetSetting", "Timeline:GetSetting" or "MediaPoolItem:GetClipProperty" without parameters (or with a NoneType or a blank property key). Using specific keys to query individual properties will
Getting values:
Invoke "Project:GetSetting", "Timeline:GetSetting" or "MediaPoolItem:GetClipProperty" with the appropriate property key. To get a snapshot of all queryable properties (keys and values), you can call
"Project:GetSetting", "Timeline:GetSetting" or "MediaPoolItem:GetClipProperty" without parameters (or with a NoneType or a blank property key). Using specific keys to query individual properties will
be faster. Note that getting a property using an invalid key will return a trivial result.
Setting values:
Invoke "Project:SetSetting", "Timeline:SetSetting" or "MediaPoolItem:SetClipProperty" with the appropriate property key and a valid value. When setting a parameter, please check the return value to
Setting values:
Invoke "Project:SetSetting", "Timeline:SetSetting" or "MediaPoolItem:SetClipProperty" with the appropriate property key and a valid value. When setting a parameter, please check the return value to
ensure the success of the operation. You can troubleshoot the validity of keys and values by setting the desired result from the UI and checking property snapshots before and after the change.
The following Project properties have specifically enumerated values:
@ -401,7 +401,7 @@ The following Project properties have specifically enumerated values:
Affects:
• x = Project:GetSetting('superScale') and Project:SetSetting('superScale', x)
"timelineFrameRate" - the property value is one of the frame rates available to the user in project settings under "Timeline frame rate" option. Drop Frame can be configured for supported frame rates
"timelineFrameRate" - the property value is one of the frame rates available to the user in project settings under "Timeline frame rate" option. Drop Frame can be configured for supported frame rates
by appending the frame rate with "DF", e.g. "29.97 DF" will enable drop frame and "29.97" will disable drop frame
Affects:
• x = Project:GetSetting('timelineFrameRate') and Project:SetSetting('timelineFrameRate', x)
@ -457,5 +457,5 @@ Project
StartRendering([idxs...]) --> Bool # Please use unique job ids (string) instead of indices.
DeleteRenderJobByIndex(idx) --> Bool # Please use unique job ids (string) instead of indices.
GetRenderJobStatus(idx) --> {status info} # Please use unique job ids (string) instead of indices.
GetSetting and SetSetting --> {} # settingName "videoMonitorUseRec601For422SDI" is no longer supported.
GetSetting and SetSetting --> {} # settingName "videoMonitorUseRec601For422SDI" is no longer supported.
# Please use "videoMonitorUseMatrixOverrideFor422SDI" and "videoMonitorMatrixOverrideFor422SDI" instead.

View file

@ -18,16 +18,14 @@ from .lib import (
get_project_manager,
get_current_project,
get_current_sequence,
add_clip_to_timeline,
create_bin,
get_video_track_names,
get_current_track_items,
get_track_item_by_name,
get_track_item_pype_tag,
set_track_item_pype_tag,
imprint,
set_publish_attribute,
get_publish_attribute,
create_current_sequence_media_bin,
create_compound_clip,
swap_clips,
get_pype_clip_metadata,
@ -76,16 +74,14 @@ __all__ = [
"get_project_manager",
"get_current_project",
"get_current_sequence",
"add_clip_to_timeline",
"create_bin",
"get_video_track_names",
"get_current_track_items",
"get_track_item_by_name",
"get_track_item_pype_tag",
"set_track_item_pype_tag",
"imprint",
"set_publish_attribute",
"get_publish_attribute",
"create_current_sequence_media_bin",
"create_compound_clip",
"swap_clips",
"get_pype_clip_metadata",

View file

@ -1,6 +1,8 @@
import sys
import json
import re
import os
import contextlib
from opentimelineio import opentime
import pype
@ -12,6 +14,7 @@ log = Logger().get_logger(__name__)
self = sys.modules[__name__]
self.project_manager = None
self.media_storage = None
# Pype sequencial rename variables
self.rename_index = 0
@ -33,6 +36,40 @@ self.temp_marker_frame = None
self.pype_timeline_name = "PypeTimeline"
@contextlib.contextmanager
def maintain_current_timeline(to_timeline: object,
from_timeline: object = None):
"""Maintain current timeline selection during context
Attributes:
from_timeline (resolve.Timeline)[optional]:
Example:
>>> print(from_timeline.GetName())
timeline1
>>> print(to_timeline.GetName())
timeline2
>>> with maintain_current_timeline(to_timeline):
... print(get_current_sequence().GetName())
timeline2
>>> print(get_current_sequence().GetName())
timeline1
"""
project = get_current_project()
working_timeline = from_timeline or project.GetCurrentTimeline()
# swith to the input timeline
project.SetCurrentTimeline(to_timeline)
try:
# do a work
yield
finally:
# put the original working timeline to context
project.SetCurrentTimeline(working_timeline)
def get_project_manager():
from . import bmdvr
if not self.project_manager:
@ -40,6 +77,13 @@ def get_project_manager():
return self.project_manager
def get_media_storage():
from . import bmdvr
if not self.media_storage:
self.media_storage = bmdvr.GetMediaStorage()
return self.media_storage
def get_current_project():
# initialize project manager
get_project_manager()
@ -52,34 +96,182 @@ def get_current_sequence(new=False):
project = get_current_project()
if new:
pmanager = get_project_manager()
new_timeline = pmanager.CreateEmptyTimeline(self.pype_timeline_name)
media_pool = project.GetMediaPool()
new_timeline = media_pool.CreateEmptyTimeline(self.pype_timeline_name)
project.SetCurrentTimeline(new_timeline)
return project.GetCurrentTimeline()
def add_clip_to_timeline(mediapool_item: object, frame_start: int,
frame_end: int) -> bool:
def create_bin(name: str, root: object = None) -> object:
"""
Adding mediaPoolItem to current timeline.
Create media pool's folder.
Return folder object and if the name does not exist it will create a new.
If the input name is with forward or backward slashes then it will create
all parents and return the last child bin object
Args:
mediapool_item (resolve.MediaPoolItem): resolve object
frame_start (int): first frame number
frame_end (int): last frame number
name (str): name of folder / bin, or hierarchycal name "parent/name"
root (resolve.Folder)[optional]: root folder / bin object
Returns:
bool: True if successful
object: resolve.Folder
"""
pmanager = get_project_manager()
# Add input clip to the current timeline:
return pmanager.AppendToTimeline([{
"mediaPoolItem": mediapool_item,
"startFrame": frame_start,
"endFrame": frame_end
}])
# get all variables
media_pool = get_current_project().GetMediaPool()
root_bin = root or media_pool.GetRootFolder()
# create hierarchy of bins in case there is slash in name
if "/" in name.replace("\\", "/"):
child_bin = None
for bname in name.split("/"):
child_bin = create_bin(bname, child_bin or root_bin)
if child_bin:
return child_bin
else:
created_bin = None
for subfolder in root_bin.GetSubFolderList():
if subfolder.GetName() in name:
created_bin = subfolder
if not created_bin:
new_folder = media_pool.AddSubFolder(root_bin, name)
media_pool.SetCurrentFolder(new_folder)
else:
media_pool.SetCurrentFolder(created_bin)
return media_pool.GetCurrentFolder()
def create_media_pool_item(fpath: str,
root: object = None) -> object:
"""
Create media pool item.
Args:
fpath (str): absolute path to a file
root (resolve.Folder)[optional]: root folder / bin object
Returns:
object: resolve.MediaPoolItem
"""
# get all variables
media_storage = get_media_storage()
media_pool = get_current_project().GetMediaPool()
root_bin = root or media_pool.GetRootFolder()
# try to search in bin if the clip does not exist
existing_mpi = get_media_pool_item(fpath, root_bin)
if not existing_mpi:
media_pool_item = media_storage.AddItemsToMediaPool(fpath)
# pop the returned dict on first item as resolve data object is such
return media_pool_item.pop(1.0)
else:
return existing_mpi
def get_media_pool_item(fpath, root: object = None) -> object:
"""
Return clip if found in folder with use of input file path.
Args:
fpath (str): absolute path to a file
root (resolve.Folder)[optional]: root folder / bin object
Returns:
object: resolve.MediaPoolItem
"""
media_pool = get_current_project().GetMediaPool()
root = root or media_pool.GetRootFolder()
fname = os.path.basename(fpath)
for _mpi in root.GetClipList():
_mpi_name = _mpi.GetClipProperty("File Name")["File Name"]
_mpi_name = get_reformated_path(_mpi_name, first=True)
if fname in _mpi_name:
return _mpi
return None
def create_timeline_item(media_pool_item: object,
timeline: object = None,
source_start: int = None,
source_end: int = None) -> object:
"""
Add media pool item to current or defined timeline.
Args:
media_pool_item (resolve.MediaPoolItem): resolve's object
timeline (resolve.Timeline)[optional]: resolve's object
source_start (int)[optional]: media source input frame (sequence frame)
source_end (int)[optional]: media source output frame (sequence frame)
Returns:
object: resolve.TimelineItem
"""
# get all variables
project = get_current_project()
media_pool = project.GetMediaPool()
clip_property = media_pool_item.GetClipProperty()
clip_name = clip_property["File Name"]
timeline = timeline or get_current_sequence()
source_start = source_start or 1003
source_end = source_end or 1005
# if timeline was used then switch it to current timeline
with maintain_current_timeline(timeline):
# Add input mediaPoolItem to clip data
clip_data = {"mediaPoolItem": media_pool_item}
# add source time range if input was given
if source_start is not None:
clip_data.update({"startFrame": source_start})
if source_end is not None:
clip_data.update({"endFrame": source_end})
print(clip_data)
# add to timeline
media_pool.AppendToTimeline([clip_data])
output_timeline_item = get_timeline_item(
media_pool_item, timeline)
assert output_timeline_item, AssertionError(
"Track Item with name `{}` doesnt exist on the timeline: `{}`".format(
clip_name, timeline.GetName()
))
return output_timeline_item
def get_timeline_item(media_pool_item: object,
timeline: object = None) -> object:
"""
Returns clips related to input mediaPoolItem.
Args:
media_pool_item (resolve.MediaPoolItem): resolve's object
timeline (resolve.Timeline)[optional]: resolve's object
Returns:
object: resolve.TimelineItem
"""
clip_property = media_pool_item.GetClipProperty()
clip_name = clip_property["File Name"]
output_timeline_item = None
timeline = timeline or get_current_sequence()
with maintain_current_timeline(timeline):
# search the timeline for the added clip
for _ti_data in get_current_track_items():
_ti_clip = _ti_data["clip"]["item"]
_ti_clip_property = _ti_clip.GetMediaPoolItem().GetClipProperty()
if clip_name in _ti_clip_property["File Name"]:
output_timeline_item = _ti_clip
return output_timeline_item
def get_video_track_names() -> list:
@ -102,6 +294,7 @@ def get_video_track_names() -> list:
def get_current_track_items(
filter: bool = False,
track_type: str = None,
track_name: str = None,
selecting_color: str = None) -> list:
""" Gets all available current timeline track items
"""
@ -117,7 +310,13 @@ def get_current_track_items(
# loop all tracks and get items
_clips = dict()
for track_index in range(1, (int(selected_track_count) + 1)):
track_name = sequence.GetTrackName(track_type, track_index)
_track_name = sequence.GetTrackName(track_type, track_index)
# filter out all unmathed track names
if track_name:
if _track_name not in track_name:
continue
track_track_items = sequence.GetItemListInTrack(
track_type, track_index)
_clips[track_index] = track_track_items
@ -126,7 +325,7 @@ def get_current_track_items(
"project": project,
"sequence": sequence,
"track": {
"name": track_name,
"name": _track_name,
"index": track_index,
"type": track_type}
}
@ -147,7 +346,7 @@ def get_current_track_items(
return selected_clips
def get_track_item_by_name(name: str) -> object:
def get_pype_track_item_by_name(name: str) -> object:
track_itmes = get_current_track_items()
for _ti in track_itmes:
tag_data = get_track_item_pype_tag(_ti["clip"]["item"])
@ -315,100 +514,6 @@ def delete_pype_marker(track_item):
self.temp_marker_frame = None
def create_current_sequence_media_bin(sequence):
seq_name = sequence.GetName()
media_pool = get_current_project().GetMediaPool()
root_folder = media_pool.GetRootFolder()
sub_folders = root_folder.GetSubFolderList()
testing_names = list()
print(f"_ sub_folders: {sub_folders}")
for subfolder in sub_folders:
subf_name = subfolder.GetName()
if seq_name in subf_name:
testing_names.append(subfolder)
else:
testing_names.append(False)
matching = next((f for f in testing_names if f is not False), None)
if not matching:
new_folder = media_pool.AddSubFolder(root_folder, seq_name)
media_pool.SetCurrentFolder(new_folder)
else:
media_pool.SetCurrentFolder(matching)
return media_pool.GetCurrentFolder()
def get_name_with_data(clip_data, presets):
"""
Take hierarchy data from presets and build name with parents data
Args:
clip_data (dict): clip data from `get_current_track_items()`
presets (dict): data from create plugin
Returns:
list: name, data
"""
def _replace_hash_to_expression(name, text):
_spl = text.split("#")
_len = (len(_spl) - 1)
_repl = f"{{{name}:0>{_len}}}"
new_text = text.replace(("#" * _len), _repl)
return new_text
# presets data
clip_name = presets["clipName"]
hierarchy = presets["hierarchy"]
hierarchy_data = presets["hierarchyData"].copy()
count_from = presets["countFrom"]
steps = presets["steps"]
# reset rename_add
if self.rename_add < count_from:
self.rename_add = count_from
# shot num calculate
if self.rename_index == 0:
shot_num = self.rename_add
else:
shot_num = self.rename_add + steps
print(f"shot_num: {shot_num}")
# clip data
_data = {
"sequence": clip_data["sequence"].GetName(),
"track": clip_data["track"]["name"].replace(" ", "_"),
"shot": shot_num
}
# solve # in test to pythonic explression
for k, v in hierarchy_data.items():
if "#" not in v:
continue
hierarchy_data[k] = _replace_hash_to_expression(k, v)
# fill up pythonic expresisons
for k, v in hierarchy_data.items():
hierarchy_data[k] = v.format(**_data)
# fill up clip name and hierarchy keys
hierarchy = hierarchy.format(**hierarchy_data)
clip_name = clip_name.format(**hierarchy_data)
self.rename_add = shot_num
print(f"shot_num: {shot_num}")
return (clip_name, {
"hierarchy": hierarchy,
"hierarchyData": hierarchy_data
})
def create_compound_clip(clip_data, name, folder):
"""
Convert timeline object into nested timeline object
@ -477,18 +582,13 @@ def create_compound_clip(clip_data, name, folder):
if c.GetName() in name), None)
print(f"_ cct created: {cct}")
# Set current timeline to created timeline:
project.SetCurrentTimeline(cct)
# Add input clip to the current timeline:
mp.AppendToTimeline([{
"mediaPoolItem": mp_item,
"startFrame": mp_first_frame,
"endFrame": mp_last_frame
}])
# Set current timeline to the working timeline:
project.SetCurrentTimeline(sq_origin)
with maintain_current_timeline(cct, sq_origin):
# Add input clip to the current timeline:
mp.AppendToTimeline([{
"mediaPoolItem": mp_item,
"startFrame": mp_first_frame,
"endFrame": mp_last_frame
}])
# Add collected metadata and attributes to the comound clip:
if mp_item.GetMetadata(self.pype_tag_name):
@ -747,3 +847,35 @@ def get_otio_clip_instance_data(otio_timeline, track_item_data):
return {"otioClip": otio_clip}
return None
def get_reformated_path(path, padded=False, first=False):
"""
Return fixed python expression path
Args:
path (str): path url or simple file name
Returns:
type: string with reformated path
Example:
get_reformated_path("plate.[0001-1008].exr") > plate.%04d.exr
"""
num_pattern = r"(\[\d+\-\d+\])"
padding_pattern = r"(\d+)(?=-)"
first_frame_pattern = re.compile(r"\[(\d+)\-\d+\]")
if "[" in path:
padding = len(re.findall(padding_pattern, path).pop())
if padded:
path = re.sub(num_pattern, f"%0{padding}d", path)
elif first:
first_frame = re.findall(first_frame_pattern, path, flags=0)
if len(first_frame) >= 1:
first_frame = first_frame[0]
path = re.sub(num_pattern, first_frame, path)
else:
path = re.sub(num_pattern, "%d", path)
return path

View file

@ -17,7 +17,7 @@ def frames_to_secons(frames, framerate):
return otio.opentime.to_seconds(rt)
def get_reformated_path(path, padded=True):
def get_reformated_path(path, padded=True, first=False):
"""
Return fixed python expression path
@ -31,14 +31,21 @@ def get_reformated_path(path, padded=True):
get_reformated_path("plate.[0001-1008].exr") > plate.%04d.exr
"""
num_pattern = "(\\[\\d+\\-\\d+\\])"
padding_pattern = "(\\d+)(?=-)"
num_pattern = r"(\[\d+\-\d+\])"
padding_pattern = r"(\d+)(?=-)"
first_frame_pattern = re.compile(r"\[(\d+)\-\d+\]")
if "[" in path:
padding = len(re.findall(padding_pattern, path).pop())
if padded:
path = re.sub(num_pattern, f"%0{padding}d", path)
elif first:
first_frame = re.findall(first_frame_pattern, path, flags=0)
if len(first_frame) >= 1:
first_frame = first_frame[0]
path = re.sub(num_pattern, first_frame, path)
else:
path = re.sub(num_pattern, f"%d", path)
path = re.sub(num_pattern, "%d", path)
return path

View file

@ -362,7 +362,7 @@ class ClipLoader:
self.sequencial_load = options.get("sequencially") or bool(
"Sequentially in order" in options.get("load_how", ""))
# try to get value from options or evaluate key value for `load_to`
self.new_sequence = options.get("newSequence") or bool(
self.new_timeline = options.get("newTimeline") or bool(
"New timeline" in options.get("load_to", ""))
assert self._populate_data(), str(
@ -374,7 +374,7 @@ class ClipLoader:
print("__init__ self.data: `{}`".format(self.data))
# add active components to class
if self.new_sequence:
if self.new_timeline:
if options.get("timeline"):
# if multiselection is set then use options sequence
self.active_timeline = options["timeline"]
@ -493,8 +493,7 @@ class ClipLoader:
self.handle_end = int(self.data["assetData"]["handleEnd"])
if self.sequencial_load:
last_track_item = lib.get_track_items(
sequence_name=self.active_timeline.name(),
last_track_item = lib.get_current_track_items(
track_name=self.active_track.name())
if len(last_track_item) == 0:
last_timeline_out = 0

View file

@ -1,24 +1,23 @@
#! python3
import sys
import DaVinciResolveScript as bmdvr
import avalon.api as avalon
import pype
def main():
resolve = bmdvr.scriptapp('Resolve')
print(f"resolve: {resolve}")
project_manager = resolve.GetProjectManager()
project = project_manager.GetCurrentProject()
media_pool = project.GetMediaPool()
root_folder = media_pool.GetRootFolder()
ls_folder = root_folder.GetClipList()
timeline = project.GetCurrentTimeline()
timeline_name = timeline.GetName()
for tl in ls_folder:
if tl.GetName() not in timeline_name:
continue
print(tl.GetName())
print(tl.GetMetadata())
print(tl.GetClipProperty())
import pype.hosts.resolve as bmdvr
# Registers pype's Global pyblish plugins
pype.install()
# activate resolve from pype
avalon.install(bmdvr)
fpath = r"C:\CODE\_PYPE_testing\testing_data\2d_shots\sh010\plate_sh010.00999.exr"
media_pool_item = bmdvr.lib.create_media_pool_item(fpath)
print(media_pool_item)
track_item = bmdvr.lib.create_timeline_item(media_pool_item)
print(track_item)
if __name__ == "__main__":