Video Source File Path from .txt file

onairmitflo

Member
I've changed the text reading function so it uses your seconds and milliseconds format.

Copy and paste this new "read_txt_file" function over the one in the MediaFromTXT.lua file, and refresh the script in OBS.
Lua:
function read_txt_file(filename)
  filename = filename or ''
  local line
  PVIDFILE = VIDFILE
  if not file_exists(filename) then return end
  for line in io.lines(filename) do
  if line ~= "" then
    VIDFILE = line
    local p, t = line:match("^([^=]+)%;(.+)$")
    if p then
        VIDPATH = p
        if not t then t = "0" end
        local tsecond = t:gsub('%.', '')
        local msecond = tsecond:gsub(',', '')
        SKIP = tonumber(msecond)
    end
  end
  end
end

I haven't yet found a way to make the skip forward in the video file any better. I'll keep trying, but referring to your other post about making a transition between the video file, if we can get that work with a fade-out/ fade-in or a dissolve between them, you wouldn't see the beginning of the video file anyway.

As I mentioned in that other post of yours, can mAirList write the path to the upcoming video file before the current one ends? If not I think the only transition that would work is the fade-out/fade-in.
mAirList can of course also specify the path from the next track, that is possible. Also from the next but one, the next but one and so on. But I think it would be more useful to create a new text file? Especially because the start time is in the TXT file. In a file would then be 2 paths to videos as well as 2 times with 2 semicolons. It is smarter to create a new TXT file, which writes all the necessary information to the next track.

;)
 

khaver

Member
Just replace this function in the current script. The function won't work in the older script.

In my pervious post I said probably only a fade-out/fade-in transition would work, but I think a stinger transition that covers the whole output frame at the point where the videos switch would also work. I'm going to start work on a new script that uses 2 scenes and 2 media sources so you can transition between them.
 

khaver

Member
Florian, here's a new version that allows a transition between the video files.

First, create 2 scenes. You can name them anything but "Scene A" and "Scene B" keeps it simple. In Scene A, create a media source and name it something like "MUSICVIDEO A". In Scene B, create another media source and name it like "MUSICVIDEO B".

Open Tools->Scripts and remove MediaFromTXT.lua from the list using the "-" button. Add MediaFromTXT2.lua. Browse to the mAirList text file. In the Scene A dropdown, select the "Scene A" scene. In the Scene A Source dropdown, select "MUSICVIDEO A". In the other dropdowns select "Scene B" and "MUSICVIDEO B". Check the "Enable script" box. Close the scripts panel and then select the transition you want to use.

It should start working as soon as the mAirList.txt file gets updated.

Let me know if you have problems.


Copy and paste this code to a new lua file named MediaFromTXT2.lua
Lua:
-- Version 2.0
o = obslua

--Set interval in ms to how often the text file is checked for changes. Default = 1000 ms (1 second).
interval = 1000

TEXTFILE= nil
VIDFILE= nil
PVIDFILE= nil
VIDPATH= nil
SKIP = nil
source_name = nil
updated = false
SCENEAB = {}
SOURCEAB = {}
togg = 1

enabled = true

function toggle(x)
local xx = x - 1
    return (1 - xx) + 1
end

function read_txt_file(filename)
  filename = filename or ''
  local line
  PVIDFILE = VIDFILE
  if not file_exists(filename) then return end
  for line in io.lines(filename) do
  if line ~= "" then
    VIDFILE = line
    local p, t = line:match("^([^=]+)%;(.+)$")
    if p then
        VIDPATH = p
        if not t then t = "0" end
        local tsecond = t:gsub('%.', '')
        local msecond = tsecond:gsub(',', '')
        SKIP = tonumber(msecond)
    end
  end
  end
end

function file_exists(file)
    local f = io.open(file, "rb")
    if f then f:close() end
    return f ~= nil
end

function script_description()
    return [[Reads the path of a video file from a selected text file and updates a selected media source using the new video file path. It reads the text file for changes at preditermined intervals.]]
end

function script_load()

end

function script_unload()

end

function refresh_media()
    local source = o.obs_get_source_by_name(SOURCEAB[togg])
    if source == nil then return end
    local settings = o.obs_data_create()
    o.obs_data_set_string(settings, "local_file", VIDPATH)
    o.obs_source_update(source, settings)
    o.obs_data_release(settings)
    o.obs_source_release(source)
    updated = true

end

function cue_media()
    local source = o.obs_get_source_by_name(SOURCEAB[togg])
    if source == nil then return end
    o.obs_source_media_set_time(source, SKIP)
    o.obs_source_release(source)
    updated = false
    local targetscene = o.obs_get_source_by_name(SCENEAB[togg])
    if targetscene then
        o.obs_frontend_set_current_scene(targetscene)
    end
    o.obs_source_release(targetscene)
    togg = toggle(togg)
end

function timer_callback()
    if updated then
        cue_media()
    else
        read_txt_file(TEXTFILE)
        if VIDFILE ~= PVIDFILE then
            refresh_media()
        --    --cue_media()
        end
    end
end

function script_properties()
    local p = o.obs_properties_create()

    o.obs_properties_add_bool(p, "enable", "Enable script")
    o.obs_properties_add_path(p, "theTextFile", "mAirList txt File",
        o.OBS_PATH_FILE,
        "Video File List (*.txt)",
        nil
    )

    local sca = o.obs_properties_add_list(p, "scenea", "Scene A", o.OBS_COMBO_TYPE_EDITABLE, o.OBS_COMBO_FORMAT_STRING)
    local objScene
    local sceneitems
    local sceneitem
    local scenes = o.obs_frontend_get_scenes()

    if scenes ~= nil then
        for _, objScene in ipairs(scenes) do
            local scene_source = o.obs_scene_from_source(objScene)
            local scenename = o.obs_source_get_name(objScene)
            o.obs_property_list_add_string(sca, scenename, scenename)
        end
        o.source_list_release(scene_source)
    end
    o.source_list_release(scenes)

    local soa = o.obs_properties_add_list(p, "sourcea", "Scene A Source", o.OBS_COMBO_TYPE_EDITABLE, o.OBS_COMBO_FORMAT_STRING)
    local sources = o.obs_enum_sources()
    if sources ~= nil then
        for _, source in ipairs(sources) do
            source_id = o.obs_source_get_id(source)
            if source_id == "ffmpeg_source" then
                local name = o.obs_source_get_name(source)
                o.obs_property_list_add_string(soa, name, name)
            end
        end
    end
    o.source_list_release(sources)

    local scb = o.obs_properties_add_list(p, "sceneb", "Scene B", o.OBS_COMBO_TYPE_EDITABLE, o.OBS_COMBO_FORMAT_STRING)
    local scenes = o.obs_frontend_get_scenes()

    if scenes ~= nil then
        for _, objScene in ipairs(scenes) do
            local scene_source = o.obs_scene_from_source(objScene)
            local scenename = o.obs_source_get_name(objScene)
            o.obs_property_list_add_string(scb, scenename, scenename)
        end
        o.source_list_release(scene_source)
    end
    o.source_list_release(scenes)

    local sob = o.obs_properties_add_list(p, "sourceb", "Scene B Source", o.OBS_COMBO_TYPE_EDITABLE, o.OBS_COMBO_FORMAT_STRING)
    local sources = o.obs_enum_sources()
    if sources ~= nil then
        for _, source in ipairs(sources) do
            local source_id = o.obs_source_get_id(source)
            if source_id == "ffmpeg_source" then
                local name = o.obs_source_get_name(source)
                o.obs_property_list_add_string(sob, name, name)
            end
        end
    end
    o.source_list_release(sources)

    return p
end

function script_defaults(s)
    o.obs_data_set_default_bool(s, "enable", false)
end

function script_update(s)
    TEXTFILE = o.obs_data_get_string(s, "theTextFile")
    table.insert(SCENEAB, o.obs_data_get_string(s, "scenea"))
    table.insert(SCENEAB, o.obs_data_get_string(s, "sceneb"))
    table.insert(SOURCEAB, o.obs_data_get_string(s, "sourcea"))
    table.insert(SOURCEAB, o.obs_data_get_string(s, "sourceb"))

    enabled = o.obs_data_get_bool(s, "enable")

    if enabled then
        print("Enabled")
        read_txt_file(TEXTFILE)
        if VIDFILE ~= PVIDFILE then
            refresh_media()
        end
        o.timer_remove(timer_callback)
        o.timer_add(timer_callback, interval)
    else
        print("Disabled")
        o.timer_remove(timer_callback)
    end

end
 

onairmitflo

Member
Florian, here's a new version that allows a transition between the video files.

First, create 2 scenes. You can name them anything but "Scene A" and "Scene B" keeps it simple. In Scene A, create a media source and name it something like "MUSICVIDEO A". In Scene B, create another media source and name it like "MUSICVIDEO B".

Open Tools->Scripts and remove MediaFromTXT.lua from the list using the "-" button. Add MediaFromTXT2.lua. Browse to the mAirList text file. In the Scene A dropdown, select the "Scene A" scene. In the Scene A Source dropdown, select "MUSICVIDEO A". In the other dropdowns select "Scene B" and "MUSICVIDEO B". Check the "Enable script" box. Close the scripts panel and then select the transition you want to use.

It should start working as soon as the mAirList.txt file gets updated.

Let me know if you have problems.


Copy and paste this code to a new lua file named MediaFromTXT2.lua
Lua:
-- Version 2.0
o = obslua

--Set interval in ms to how often the text file is checked for changes. Default = 1000 ms (1 second).
interval = 1000

TEXTFILE= nil
VIDFILE= nil
PVIDFILE= nil
VIDPATH= nil
SKIP = nil
source_name = nil
updated = false
SCENEAB = {}
SOURCEAB = {}
togg = 1

enabled = true

function toggle(x)
local xx = x - 1
    return (1 - xx) + 1
end

function read_txt_file(filename)
  filename = filename or ''
  local line
  PVIDFILE = VIDFILE
  if not file_exists(filename) then return end
  for line in io.lines(filename) do
  if line ~= "" then
    VIDFILE = line
    local p, t = line:match("^([^=]+)%;(.+)$")
    if p then
        VIDPATH = p
        if not t then t = "0" end
        local tsecond = t:gsub('%.', '')
        local msecond = tsecond:gsub(',', '')
        SKIP = tonumber(msecond)
    end
  end
  end
end

function file_exists(file)
    local f = io.open(file, "rb")
    if f then f:close() end
    return f ~= nil
end

function script_description()
    return [[Reads the path of a video file from a selected text file and updates a selected media source using the new video file path. It reads the text file for changes at preditermined intervals.]]
end

function script_load()

end

function script_unload()

end

function refresh_media()
    local source = o.obs_get_source_by_name(SOURCEAB[togg])
    if source == nil then return end
    local settings = o.obs_data_create()
    o.obs_data_set_string(settings, "local_file", VIDPATH)
    o.obs_source_update(source, settings)
    o.obs_data_release(settings)
    o.obs_source_release(source)
    updated = true

end

function cue_media()
    local source = o.obs_get_source_by_name(SOURCEAB[togg])
    if source == nil then return end
    o.obs_source_media_set_time(source, SKIP)
    o.obs_source_release(source)
    updated = false
    local targetscene = o.obs_get_source_by_name(SCENEAB[togg])
    if targetscene then
        o.obs_frontend_set_current_scene(targetscene)
    end
    o.obs_source_release(targetscene)
    togg = toggle(togg)
end

function timer_callback()
    if updated then
        cue_media()
    else
        read_txt_file(TEXTFILE)
        if VIDFILE ~= PVIDFILE then
            refresh_media()
        --    --cue_media()
        end
    end
end

function script_properties()
    local p = o.obs_properties_create()

    o.obs_properties_add_bool(p, "enable", "Enable script")
    o.obs_properties_add_path(p, "theTextFile", "mAirList txt File",
        o.OBS_PATH_FILE,
        "Video File List (*.txt)",
        nil
    )

    local sca = o.obs_properties_add_list(p, "scenea", "Scene A", o.OBS_COMBO_TYPE_EDITABLE, o.OBS_COMBO_FORMAT_STRING)
    local objScene
    local sceneitems
    local sceneitem
    local scenes = o.obs_frontend_get_scenes()

    if scenes ~= nil then
        for _, objScene in ipairs(scenes) do
            local scene_source = o.obs_scene_from_source(objScene)
            local scenename = o.obs_source_get_name(objScene)
            o.obs_property_list_add_string(sca, scenename, scenename)
        end
        o.source_list_release(scene_source)
    end
    o.source_list_release(scenes)

    local soa = o.obs_properties_add_list(p, "sourcea", "Scene A Source", o.OBS_COMBO_TYPE_EDITABLE, o.OBS_COMBO_FORMAT_STRING)
    local sources = o.obs_enum_sources()
    if sources ~= nil then
        for _, source in ipairs(sources) do
            source_id = o.obs_source_get_id(source)
            if source_id == "ffmpeg_source" then
                local name = o.obs_source_get_name(source)
                o.obs_property_list_add_string(soa, name, name)
            end
        end
    end
    o.source_list_release(sources)

    local scb = o.obs_properties_add_list(p, "sceneb", "Scene B", o.OBS_COMBO_TYPE_EDITABLE, o.OBS_COMBO_FORMAT_STRING)
    local scenes = o.obs_frontend_get_scenes()

    if scenes ~= nil then
        for _, objScene in ipairs(scenes) do
            local scene_source = o.obs_scene_from_source(objScene)
            local scenename = o.obs_source_get_name(objScene)
            o.obs_property_list_add_string(scb, scenename, scenename)
        end
        o.source_list_release(scene_source)
    end
    o.source_list_release(scenes)

    local sob = o.obs_properties_add_list(p, "sourceb", "Scene B Source", o.OBS_COMBO_TYPE_EDITABLE, o.OBS_COMBO_FORMAT_STRING)
    local sources = o.obs_enum_sources()
    if sources ~= nil then
        for _, source in ipairs(sources) do
            local source_id = o.obs_source_get_id(source)
            if source_id == "ffmpeg_source" then
                local name = o.obs_source_get_name(source)
                o.obs_property_list_add_string(sob, name, name)
            end
        end
    end
    o.source_list_release(sources)

    return p
end

function script_defaults(s)
    o.obs_data_set_default_bool(s, "enable", false)
end

function script_update(s)
    TEXTFILE = o.obs_data_get_string(s, "theTextFile")
    table.insert(SCENEAB, o.obs_data_get_string(s, "scenea"))
    table.insert(SCENEAB, o.obs_data_get_string(s, "sceneb"))
    table.insert(SOURCEAB, o.obs_data_get_string(s, "sourcea"))
    table.insert(SOURCEAB, o.obs_data_get_string(s, "sourceb"))

    enabled = o.obs_data_get_bool(s, "enable")

    if enabled then
        print("Enabled")
        read_txt_file(TEXTFILE)
        if VIDFILE ~= PVIDFILE then
            refresh_media()
        end
        o.timer_remove(timer_callback)
        o.timer_add(timer_callback, interval)
    else
        print("Disabled")
        o.timer_remove(timer_callback)
    end

end
Hey, first thank you for your effort! How should the TXT file of mAirList look like with this new script, because now two paths with respective start time are handled. Maybe like this?

13.02_6ihIVlf5HM.png


In that case the current running track and the next upcoming track are written below each other. How should it be?

Greetings
Florian
 

onairmitflo

Member
Hey, first thank you for your effort! How should the TXT file of mAirList look like with this new script, because now two paths with respective start time are handled. Maybe like this?

13.02_6ihIVlf5HM.png


In that case the current running track and the next upcoming track are written below each other. How should it be?

Greetings
Florian
THAT HAS BEEN CLEARED UP! I have only just been able to try it out and I am amazed. The transition is so clean! So beautiful! However, the start time currently does not work. The music video does not start, at the given time in the text file.

I'm sure you'll find a solution.

Anyway, thank you so much!!! THANK YOU!
 

khaver

Member
The text file needs to be just like before. The single currently playing path. When you first load OBS with the script already loaded and it's parameters already filled in, the video file and skip time are loaded into the MUSICVIDEO A source and the scene is set to SCENE A. When mAirList overwrites the path and skip time in the file, that video file is loaded into MUSICVIDEO B and switches to SCENE B with the transition you've set. This goes back and forth each time the text file is changed by mAirList.

The skip times you show in your example above are quite small, only a few frames. I think the values you have in the text file are rounded to the nearest frame by OBS.
 

onairmitflo

Member
The skip times you show in your example above are quite small, only a few frames. I think the values you have in the text file are rounded to the nearest frame by OBS.
However, the start time does not seem to work in general. I had tried a start time of 5 seconds or 24 seconds earlier, but it didn't seem to be applied. :/
 

khaver

Member
Make sure in both MUSICVIDEO sources you don't have "Loop", "Restart playback when source becomes active", and "Close file when inactive" checked.
 

onairmitflo

Member
Great. Let me know if you need changes or you run into problems.
So unfortunately I have the problem that the start time is quite inaccurate and sometimes does not work at all. For example, a start time of 0:13:100 has shown that it does not work at all.
14.02_N6A6L0WdF4.png

In the text file 13.1 seconds are written, which is not wrong at all from the time. Nevertheless, the script seems to really disregard the descendant numbers, which becomes really advantageous for voice synchronous music videos. In this case, if the after comma number is only "1" and not "100" this is not applied at all. However, I can't influence that through mAirList.

I tried it and instead of "13.1" seconds I wrote in "13.100". If there are 3 numbers after the decimal point, the start time on the video works. With 13,1 seconds not. Also, I don't think the milliseconds are applied to the video at all, are they? As mentioned above, lip sync.
 

khaver

Member
Okay, I assumed the time format would always have 3 digits after the decimal point so I just removed the decimal point to get the milliseconds. 2.123 or 2,123 would be 2123 milliseconds. I will fix the script to adjust to the number of digits after the decimal point. Quick question, will the time format ever contain the EU style thousands separator "." or will time over 999 seconds just be numbers without the "." separator? i.e 12.777,23 or 12777,23?
 

PedjaS

Member
You may set two scenes that are identical and have video source so this script could update video source to those scenes in alternate order and then change to that alternate scene meaning each video starts in alternate scene. That would provide means for transition.

I would suggest adding end time in the file. Some videos need to be trimmed at the end too.
 

onairmitflo

Member
Okay, I assumed the time format would always have 3 digits after the decimal point so I just removed the decimal point to get the milliseconds. 2.123 or 2,123 would be 2123 milliseconds. I will fix the script to adjust to the number of digits after the decimal point. Quick question, will the time format ever contain the EU style thousands separator "." or will time over 999 seconds just be numbers without the "." separator? i.e 12.777,23 or 12777,23?
The latter, even with larger starting units, perhaps over 1h the result comes out without a point. For example:
14.02_kui73jko4r.png
 

onairmitflo

Member
You may set two scenes that are identical and have video source so this script could update video source to those scenes in alternate order menaing each video starts in alternate scene and then change to that scene. That would provide means for transition.

I would suggest adding end time in the file. Some videos need to be trimmed at the end too.
Both of these are moot. The script currently works with 2 scenes and an automatically controlled transition. Also, the end time does not need to be added, because mAirList always overwrites the file when a new song starts. So the song can start earlier, even if the video itself is not yet over (outro be like), there would then be a transition.
 

khaver

Member
Copy and paste this read_txt_file function over the one in your MediaFromTXT2.lua file. Refresh in OBS and see if it works now.

Lua:
function read_txt_file(filename)
  filename = filename or ''
  local line
  PVIDFILE = VIDFILE
  if not file_exists(filename) then return end
  for line in io.lines(filename) do
  if line ~= "" then
    VIDFILE = line
    local p, t = line:match("^([^=]+)%;(.+)$")
    if p then
        VIDPATH = p
        if not t then t = "0" end
        local tsecond = t:gsub('%.', '')
        local ssecond = tsecond:gsub(',', '%.')
        ssecond = tonumber(ssecond)
        local milli = ssecond * 1000.0
        SKIP = milli
    end
  end
  end
end
 

onairmitflo

Member
Copy and paste this read_txt_file function over the one in your MediaFromTXT2.lua file. Refresh in OBS and see if it works now.

Lua:
function read_txt_file(filename)
  filename = filename or ''
  local line
  PVIDFILE = VIDFILE
  if not file_exists(filename) then return end
  for line in io.lines(filename) do
  if line ~= "" then
    VIDFILE = line
    local p, t = line:match("^([^=]+)%;(.+)$")
    if p then
        VIDPATH = p
        if not t then t = "0" end
        local tsecond = t:gsub('%.', '')
        local ssecond = tsecond:gsub(',', '%.')
        ssecond = tonumber(ssecond)
        local milli = ssecond * 1000.0
        SKIP = milli
    end
  end
  end
end
Okay. That works for now. However, I am a bit confused right now. Is it possible that there is a delay with every song change? I have to keep delaying the audio in mAirList to get it lip-synced. Do me a favor: please remove the transition, I'll try it myself with the Advanced Scene Changer and look into the TXT file. Then I'll give you feedback if that did anything. Thank you!
 

onairmitflo

Member
Copy and paste this read_txt_file function over the one in your MediaFromTXT2.lua file. Refresh in OBS and see if it works now.

Lua:
function read_txt_file(filename)
  filename = filename or ''
  local line
  PVIDFILE = VIDFILE
  if not file_exists(filename) then return end
  for line in io.lines(filename) do
  if line ~= "" then
    VIDFILE = line
    local p, t = line:match("^([^=]+)%;(.+)$")
    if p then
        VIDPATH = p
        if not t then t = "0" end
        local tsecond = t:gsub('%.', '')
        local ssecond = tsecond:gsub(',', '%.')
        ssecond = tonumber(ssecond)
        local milli = ssecond * 1000.0
        SKIP = milli
    end
  end
  end
end
So make it so that the content of the two sources always alternate, but no transition is triggered.
 
Top