[vlc-devel] [PATCH] dcp: Creation of LUA script to read DCP
Pierre Villard
pierre.villard.fr at gmail.com
Tue Mar 5 17:24:03 CET 2013
Hi.
Sorry for this new message. I am not familiar with git send-mail command.
This patch is about a script Lua in order to allow reading Digital
Cinema Packages.
Use : ./vlc path/to/dcp/directory
The function probe() returns true if a "ASSETMAP" file is found in the
DCP Directory (input).
The script parses the XML files to retrieve audio and video files.
Then the script returns a playlist with the video file to play and the
audio file as an input-slave.
This script has been developed as part of a project in an engineering
school.
The supervisor is Nicolas BERTRAND.
Thanks.
> ---
> share/lua/playlist/dcp.lua | 330 ++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 330 insertions(+)
> create mode 100644 share/lua/playlist/dcp.lua
>
> diff --git a/share/lua/playlist/dcp.lua b/share/lua/playlist/dcp.lua
> new file mode 100644
> index 0000000..55bfa4d
> --- /dev/null
> +++ b/share/lua/playlist/dcp.lua
> @@ -0,0 +1,330 @@
> +--[[
> + $Id$
> +
> + Copyright © 2013 VideoLAN and AUTHORS
> +
> + Author:
> + - Pierre VILLARD <pierre dot villard dot fr at gmail dot com>
> +
> + This script has been developped as part of a project with an
> + engineering school (ENSEEIHT : www.enseeiht.fr). The aim is to
> + provide a solution to read Digital Cinema Packages. The project group
> + is composed of Claire Etienne, Aurélie Sbinné, Julien Puyobro,
> + Samuel Kerjose and Pierre Villard.
> +
> + To be used, the user has to provide a directory as input data to VLC :
> + ./vlc DCP/directory/path
> +
> + The probe function checks if there is a "ASSETMAP" file in the direcotry.
> +
> + TODO : handle subtitles
> + TODO : add a solution to handle multiple audio files
> + FIXME : "/" and "\" conflict according to operating systems
> +
> + 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.
> +--]]
> +
> +-- Function to check if a file exists or not
> +-- We open a handler on the file, if the handler is null, the file
> +-- does not exist. If the file exists, we close the handler.
> +-- @param path Path to the file
> +-- @return true if the file exists, false otherwise
> +function is_file( path )
> + local handle = io.open( path )
> + local result = not ( nil == handle ) -- true if the file exists
> + if( result ) then -- if the file exists
> + io.close( handle )
> + end
> + return result
> +end
> +
> +-- Function to add a slash at the end of the path if necessary
> +-- /dcp/path/ => /dcp/path/
> +-- /dcp/path => /dcp/path/
> +-- @param path Path
> +-- @return Path with a single slash at the end
> +-- FIXME May be it works only on Linux operating systems ("/" and "\")
> +function add_slash( path )
> + local reverse = string.reverse( path )
> + if( string.find( reverse, "/" ) == 1 ) then
> + return path
> + else
> + return path .. "/"
> + end
> +end
> +
> +-- Probe function : this function is mandatory to make this script
> +-- working with VLC. If the function returns true, it means this script
> +-- is appropriate te be used in order to parse input data given by VLC.
> +-- For DCP, we return true is there is an "ASSETMAP" file in the directory
> +-- @return true if there is an "ASSETMAP" file in the directory
> +function probe()
> + local full_path = add_slash( vlc.path )
> +
> + -- possibilites to write "ASSETMAP" file name
> + -- there is no accurate specification
> + local path_lower_xml = full_path .. "assetmap.xml"
> + local path_lower_xml_upper = full_path .. "assetmap.XML"
> + local path_lower_notxml = full_path .. "assetmap"
> + local path_upper_xml = full_path .. "ASSETMAP.xml"
> + local path_upper_xml_upper = full_path .. "ASSETMAP.XML"
> + local path_upper_notxml = full_path .. "ASSETMAP"
> +
> + -- Debug purpose
> + -- vlc.msg.dbg( "DCP Probe function with : " .. vlc.path )
> +
> + local res = is_file( path_lower_xml ) or
> + is_file( path_lower_xml_upper ) or
> + is_file( path_lower_notxml ) or
> + is_file( path_upper_xml ) or
> + is_file( path_upper_xml_upper ) or
> + is_file( path_upper_notxml )
> +
> + -- Debug purpose
> + -- if res then
> + -- vlc.msg.dbg( "Probe function : OK" )
> + -- else
> + -- vlc.msg.dbg( "Probe function : KO (you should consider the option --demux=any or modify your vlcrc file)" )
> + -- end
> +
> + return res
> +end
> +
> +-- Function to get the path to the ASSETMAP file
> +-- @param path Path of the DCP directory
> +-- @return Path to the ASSETMAP file
> +function get_assetmap( path )
> + -- possibilites to write "ASSETMAP" file name
> + -- there is no accurate specification
> + local path_lower_xml = path .. "assetmap.xml"
> + local path_lower_xml_upper = path .. "assetmap.XML"
> + local path_lower_notxml = path .. "assetmap"
> + local path_upper_xml = path .. "ASSETMAP.xml"
> + local path_upper_xml_upper = path .. "ASSETMAP.XML"
> + local path_upper_notxml = path .. "ASSETMAP"
> +
> + if( is_file( path_lower_xml ) ) then
> + return path_lower_xml
> + elseif( is_file( path_lower_xml_upper ) ) then
> + return path_lower_xml_upper
> + elseif( is_file( path_lower_notxml ) ) then
> + return path_lower_notxml
> + elseif( is_file( path_upper_xml ) ) then
> + return path_upper_xml
> + elseif( is_file( path_upper_xml_upper ) ) then
> + return path_upper_xml_upper
> + else
> + return path_upper_notxml
> + end
> +end
> +
> +-- Function used to remove all the spaces in the line before a
> +-- given tag. Ex : (" <Id>foo</Id>", "<Id>") => "<Id>foo</Id>"
> +-- This function has been created to prevent deletion of spaces in
> +-- file names. Ex : " <Path>my video.mxf</Path>"
> +-- @param line Text to clear
> +-- @param start Tag delimiter
> +-- @return Text cleared of spaces before the tag
> +function remove_spaces_before( line, start )
> + local nb = string.find( line, start )
> + local text, occ = string.gsub( line, "%s", "", nb - 1 )
> + return text
> +end
> +
> +-- Function used to parse ASSETMAP file
> +-- We parse the file and we store in a table information
> +-- about the files described in the ASSETMAP file.
> +-- We only take into account .xml files (CPL and PKL files)
> +-- and .mxf files (Video, Audio and subtitles [TODO : handle subtitles])
> +-- @param path Path to the ASSETMAP file
> +-- @return Table with information about files (ID, Path and Type=PKL|CPL|MXF)
> +-- @return Name of the CPL file
> +function parse_assetmap( path )
> + assetlist = false
> + pklfile = false
> + cpl_path = nil
> + files = {}
> +
> + -- we parse the file line by line
> + -- this operation closes the file at the end of the for statement
> + for line in io.lines( path ) do
> +
> + -- we only consider files between <AssetList> and </AssetList>
> + if( string.match( line, "AssetList" ) ) then
> + assetlist = not( assetlist )
> + end
> +
> + if( assetlist ) then
> +
> + -- This block determines if we currently are collection
> + -- information about the PKL file : because this file is the
> + -- only one to be described with <PackingList>true</PackingList>
> + if( string.match( line, "PackingList" ) ) then
> + pklfile = true
> + end
> + if( string.match( line, "Asset" ) and pklfile ) then
> + pklfile = false
> + end
> +
> + -- This block determines the ID of the file
> + if( string.match( line, "<Id>" ) ) then
> + text = remove_spaces_before( line, "<Id>" )
> + id_file = string.sub( text, 5, -6 )
> + end
> +
> + -- This block determines the Path of the file
> + if( string.match( line, "<Path>" ) ) then
> + text = remove_spaces_before( line, "<Path>" )
> + path_file = string.sub( text, 7, -8 )
> +
> + -- If the file is a XML file
> + if( string.match( path_file, ".xml" ) or string.match( path_file, ".XML" ) ) then
> + if( pklfile ) then -- If it is the PKL file (for the type)
> + table.insert( files, { type_file = "PKL",
> + path = path_file,
> + id = id_file } )
> + else -- otherwise it is the CPL file (for the type)
> + table.insert( files, { type_file = "CPL",
> + path = path_file,
> + id = id_file } )
> + cpl_path = path_file
> + end
> + -- If the file is a MXF file
> + elseif( string.match( path_file, ".mxf" ) or string.match( path_file, ".MXF" ) ) then
> + table.insert( files, { type_file = "MXF",
> + path = path_file,
> + id = id_file } )
> + else -- otherwise Error Message with ID and Path
> + vlc.msg.err( id_file .. " - " .. path_file )
> + end
> + end
> +
> + end
> + end
> + return files, cpl_path -- return table of information and CPL file name
> +end
> +
> +-- Function used to parse CPL file
> +-- We parse the file in order to get the ID of the Video and the Audio
> +-- files (TODO : handle subtitles). We also get the title of the content which
> +-- will be displayed by VLC. Video file is described between <MainPicture> and
> +-- </MainPicture> and Audio file is described between <MainSound> and </MainSound>
> +-- Title is given between <ContentTitleText> and </ContentTitleText>
> +-- @param path Path to the CPL file
> +-- @return ID of the video file
> +-- @return ID of the audio file
> +-- @return Title to display for the content
> +function parse_cplfile( cpl_path )
> + is_main_picture = false
> + is_main_sound = false
> + main_picture_id = nil
> + main_sound_id = nil
> + title = nil
> +
> + -- We parse the file line by line
> + for line in io.lines( cpl_path ) do
> +
> + -- This block determines if we are between <MainPicture> and </MainPicture>
> + if( string.match( line, "MainPicture" ) ) then
> + is_main_picture = not ( is_main_picture )
> + end
> +
> + -- This block determines if we are between <MainSound> and </MainSound>
> + if( string.match( line, "MainSound" ) ) then
> + is_main_sound = not ( is_main_sound )
> + end
> +
> + -- We get the ID of the file if it is about audio or video file
> + if( string.match( line, "<Id>" ) and ( is_main_sound or is_main_picture ) ) then
> + -- we remove spaces in the line
> + text = remove_spaces_before( line, "<Id>" )
> + id_file = string.sub( text, 5, -6 )
> + if( is_main_picture ) then
> + main_picture_id = id_file
> + else
> + main_sound_id = id_file
> + end
> + end
> +
> + -- We get the title to display
> + if( string.match( line, "ContentTitleText" ) ) then
> + text = remove_spaces_before( line, "<ContentTitleText>" )
> + title = string.sub( text, 19, -20 )
> + end
> + end
> + return main_picture_id, main_sound_id, title
> + -- return ID of video file and audio file and title to display
> +end
> +
> +-- Function to get the Name information corresponding to the given ID
> +-- @param files Tables of information
> +-- @param id ID to find in the table
> +-- @return Name of the file corresponding to the given ID
> +function get_name_for_id( files, id )
> + for v = 1, table.getn( files ), 1 do
> + id_table = files[v].id
> + path_table = files[v].path
> + if( id_table == id ) then
> + return path_table
> + end
> + end
> +end
> +
> +-- Parse function : this function is mandatory to make this script
> +-- working with VLC. This function parses input data to return the
> +-- playlist VLC has to play with the correct parameters. In this
> +-- case we return one track, the video, with a parameter, the audio
> +-- file as an input-slave. TODO : handle subtitles
> +-- @return Playlist to play
> +function parse()
> + -- We get the full path (with a "/" at the end)
> + local full_path = add_slash( vlc.path )
> +
> + -- We get the path to the ASSETMAP file
> + local assetmap = get_assetmap( full_path )
> +
> + -- We parse the file to get information table about the files
> + -- and to get the CPL file name
> + files, cpl_name = parse_assetmap( assetmap )
> +
> + -- We get the path to the CPL file
> + cpl_path = full_path .. cpl_name
> +
> + -- We parse the CPL file to get IDs of Video and Audio files, and title to display
> + main_picture_id, main_sound_id, title_to_display = parse_cplfile( cpl_path )
> +
> + -- We retrieve file names for Audio and Video in the table
> + main_picture_name = get_name_for_id( files, main_picture_id )
> + main_sound_name = get_name_for_id( files, main_sound_id )
> +
> + -- Debug purpose
> + vlc.msg.dbg( "Video file path = " .. full_path .. main_picture_name )
> + vlc.msg.dbg( "Audio file path = " .. full_path .. main_sound_name )
> +
> + -- We get the paths to video and audio files
> + local video = "file://" .. full_path .. main_picture_name
> + local audio = "file://" .. full_path .. main_sound_name
> +
> + -- We create the playlist to play
> + -- With a specific option 'input-slave' for the audio
> + local tracks = {}
> + table.insert( tracks, { path = video,
> + title = title_to_display,
> + arturl = nil,
> + options = {':input-slave=' .. audio} } )
> +
> + return tracks -- we return the playlist
> +end
> +
More information about the vlc-devel
mailing list