Skip to content

Instantly share code, notes, and snippets.

@thiagottjv
Forked from greatdg/addSimilarVideo.lua
Last active March 3, 2023 01:27
Show Gist options
  • Save thiagottjv/e37ec8c981ec3b5ca6bc to your computer and use it in GitHub Desktop.
Save thiagottjv/e37ec8c981ec3b5ca6bc to your computer and use it in GitHub Desktop.
Updating file to work without the vlc.net.opendir().
--[[
Add Similar, testing version
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
--]]
--[[ Extension description ]]
function descriptor()
return { title = "Add Similar" ;
version = "alpha" ;
author = "VideoLAN" ;
shortdesc = "Quickly adds files similar to the one you're playing";
description = "<h1>Add Similar</h1>"
.. "When you're playing a file, use Add Similar to"
.. "easily add files that are similar (like those in a series).";
capabilities = { "input-listener", "meta-listener" } }
end
--[[ Global vars ]]
path = nil
target = nil
os = nil
slash = nil
--[[ Hooks ]]
-- Activation hook
function activate()
vlc.msg.dbg("[Add Similar] Activated")
start()
end
-- Deactivation hook
function deactivate()
vlc.msg.dbg("[Add Similar] Deactivated")
end
--[[ Start ]]
function start()
item = vlc.item or vlc.input.item() -- check if an item is playing
--Check if windows or linux
checkWindows()
if not item then -- return an alert box explaining what the user should do
alert("Using Add Similar",
"When you're playing a file, use Add Similar to "
.. "automatically add other files in its folder that are like "
.. "the one you're playing."
.."<br /><br />To use Add Similar, start playing a file.")
vlc.msg.dbg("[Add Similar] No item has been selected.")
else
find_similar(item) -- starts the "Find Similar" process
end
end
function find_similar(item)
vlc.msg.dbg("[Add Similar] File selected: "..vlc.strings.decode_uri((item:uri())))
-- Check the protocol (currently, only file:/// is supported)
if string.find(item:uri(), "file:///") then
similar_media = find_files(item) -- delegate to the find_files
--vlc.msg.dbg("[Add Similar] Files found: "..table.concat(similar_media," , "))
else
similar_media = {}
vlc.msg.dbg("[Add Similar] Unsupported media type.")
alert("Unsupported media type", "We're sorry, but Add Similar only works on local files right now.")
end
-- Did it return anything?
if #similar_media == 0 then -- If nothing was returned, inform the user
vlc.msg.info("[Add Similar] didn't find similar files")
alert("No similar files could be found.", "")
else -- Add the files
vlc.msg.info("[Add Similar] found similar files")
for _,file in pairs(similar_media) do
local new_item = {}
vlc.msg.dbg("file://"..path..file)
new_item.path = "file://"..vlc.strings.encode_uri_component(path)..file
new_item.name = file
vlc.msg.dbg("[Add Similar] adding: "..path..file)
vlc.playlist.enqueue({new_item})
end
vlc.deactivate()
end
end
-- This function will find files and add them to a table, which it returns.
function find_files(item)
-- Scour the directory for files (Starts at position 9 to get rid of "file:///") (TODO: Clean this up!)
path = vlc.strings.decode_uri(string.sub(item:uri(), 8 , -(string.find(string.reverse(item:uri()), "/", 0, true))))
target = vlc.strings.decode_uri(string.sub(item:uri(), string.len(item:uri())+2-(string.find(string.reverse(item:uri()), "/", 0, true))), -0)
support_extensions = Set {'mpeg2','mp3','dv','mod','gxf','mpeg4','dvr-ms','qt','m2p','ogg','m2v','ts','m2t','ogv','mov','3gp2','aac','mp4','mkv','vro','flc','wmv','wma','3g2','m2ts','divx','fli','flv','webm','avi','vob','mts','rmvb','ogm','m1v','mpv','wav','swf','asf','mpe','3gpp','mpg','f4v','rm','tod','trp','amv','3gp','dat','m4a','mpeg','avs','nsv','m4v','ifo','tp'}
vlc.msg.dbg("[Add Similar] Target file: "..target)
vlc.msg.dbg("[Add Similar] Loading directory: "..path)
-- Load directory contents into a table
local contents = list_dir(path)
table.sort(contents)
-- TEST 1: Split the filename into words
local keywords = split(target,"[%p%s]")
vlc.msg.dbg("[Add Similar] Keywords: "..table.concat(keywords," \\ "))
-- TEST 2: Analyze the filename to find its structure
local structure = split(target,"[^%p]+")
vlc.msg.dbg("[Add Similar] Structure: "..table.concat(structure))
-- TEST 3: Look for keywords (second pass)
score= {}
started = false
for _, file in pairs(contents) do
vlc.msg.dbg("[Add Similar] "..file)
extention = string.lower(string.sub(file, string.find(string.reverse(file), ".", 0, true)*-1+1))
if not (file == target or file == '.' or file == '..') and (support_extensions[extention] == true and started == true) then
-- TEST 1: Look for matches in words in the file
local file_keywords = split(file,"[%p%s]")
local matches = 0
for _,key in pairs(keywords) do
for _,file_key in pairs(file_keywords) do
if (file_key == key) then
matches = matches + 1
end
end
end
vlc.msg.dbg(" Matches: "..matches)
vlc.msg.dbg(" Probability: "..(matches/#keywords))
increment = ((matches/#keywords)*100)*0.40
score[file] = score[file] or 0
score[file] = score[file] + increment
vlc.msg.dbg(" Score added: "..increment)
-- TEST 2: Look for similarities in file structure
local file_structure = split(target,"[^%p]+")
if table.concat(file_structure)==table.concat(structure) then
increment = 40
vlc.msg.dbg(" Structure match: true")
score[file] = score[file] or 0
score[file] = score[file] + increment
vlc.msg.dbg(" Score added: "..increment)
end
score[file] = score[file] or 0
vlc.msg.dbg(" Final score: "..score[file])
else
vlc.msg.dbg(" Not analyzed")
end
if file == target then
started = true
end
end
local cutoff = 60 -- arbitrary value
-- Return similar media
local similar_media = {}
for file,p in pairsByKeys(score) do
if p > cutoff then
table.insert(similar_media,file)
end
end
return similar_media
end
function list_dir(path)
if not path or trim(path) == "" then return false end
local dir_list_cmd
local list = {}
if not is_dir(path) then return false end
path = string.gsub(path, "^/(.*)/?$", "%1")
path = string.gsub(path, "^(.-)[\\/]?$", "%1")
if os == "win" then
dir_list_cmd = io.popen('dir /b "'..path..'"')
elseif os == "lin" then
dir_list_cmd = io.popen('ls -1 "'..path..'"')
end
if dir_list_cmd then
for filename in dir_list_cmd:lines() do
if string.match(filename, "^[^%s]+.+$") then
table.insert(list, filename)
end
end
return list
else
return false
end
end
function is_dir(path)
if not path or trim(path) == ""
then return false end
-- Remove slash at the end or it won't work on Windows
path = string.gsub(path, "^/(.*)/?$", "%1")
path = string.gsub(path, "^(.-)[\\/]?$", "%1")
local f, _, code = io.open(path, "rb")
if f then
_, _, code = f:read("*a")
f:close()
if code == 21 then
return true
end
elseif code == 13 then
return true
end
return false
end
function pairsByKeys (t, f)
local a = {}
for n in pairs(t) do table.insert(a, n) end
table.sort(a, f)
local i = 0 -- iterator variable
local iter = function () -- iterator function
i = i + 1
if a[i] == nil then return nil
else return a[i], t[a[i]]
end
end
return iter
end
--[[ UTILITY FUNCTIONS ]]--
-- Create an alertbox, also quitting the extension
function alert(title,message)
local dialog = vlc.dialog("Add Similar")
dialog:add_label("<h2>"..title.."</h2>", 1, 1, 5, 10)
dialog:add_label(message, 14, 5, 5, 200)
dialog:add_button("OK", function () dialog:delete() vlc.deactivate(); return nil end, 3, 20, 1, 5)
end
function close() -- when the messagebox is closed
vlc.deactivate()
end
-- Splits a string into a table, based on a patterns (from: http://http://lua-users.org/wiki/SplitJoin )
function split(str, pat)
local t = {} -- NOTE: use {n = 0} in Lua-5.0
local fpat = "(.-)" .. pat
local last_end = 1
local s, e, cap = str:find(fpat, 1)
while s do
if s ~= 1 or cap ~= "" then
table.insert(t,cap)
end
last_end = e+1
s, e, cap = str:find(fpat, last_end)
end
if last_end <= #str then
cap = str:sub(last_end)
table.insert(t,cap)
end
return t
end
function Set (list)
local set = {}
for _, l in ipairs(list) do set[l] = true end
return set
end
function trim(str)
if not str then return "" end
return string.gsub(str, "^[\r\n%s]*(.-)[\r\n%s]*$", "%1")
end
function checkWindows(path)
if string.match(vlc.config.datadir(), "^(%a:.+)$") then
os = "win"
slash = "\\"
else
os = "lin"
slash = "/"
end
end
@Aaron-Ritter-IDEXX
Copy link

Aaron-Ritter-IDEXX commented Oct 11, 2017

On directories with files with no extensions, it fails due to the string.find function returning nil. I suggest this edit at line 140:

		local periodIndex = string.find(string.reverse(file), ".", 0, true)
		if periodIndex ~= nil then 
			extention = string.lower(string.sub(file, periodIndex*-1+1))

and insert another "end" after the score stuff.

@udvarit
Copy link

udvarit commented Feb 17, 2022

Works like a charm, thanks Aaron, I appreciate it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment