diff --git a/Project/Gem/mediainfolib.gemspec b/Project/Gem/mediainfolib.gemspec new file mode 100644 index 000000000..3fb8a42f6 --- /dev/null +++ b/Project/Gem/mediainfolib.gemspec @@ -0,0 +1,23 @@ +require 'fileutils' + +Dir.mkdir('lib') unless File.exists?('lib') +FileUtils.cp('../../Source/MediaInfoDLL/MediaInfoDLL.rb', 'lib/mediainfolib.rb') + +Gem::Specification.new do |s| + s.name = 'mediainfolib' + s.version = '18.08.1' + s.license = 'BSD-2-Clause' + s.summary = 'Get most relevant technical and tag data for video and audio files' + s.author = 'MediaArea.net SARL' + s.email = 'info@mediaarea.net' + s.files = ['lib/mediainfolib.rb'] + s.homepage = 'https://mediaarea.net/MediaInfo' + s.requirements = 'MediaInfoLib native library in OS library search path' + s.metadata = { + "homepage_uri" => "https://mediaarea.net/MediaInfo", + "changelog_uri" => "https://mediaarea.net/MediaInfo/ChangeLog", + "source_code_uri" => "https://github.com/MediaArea/MediaInfoLib", + "bug_tracker_uri" => "https://github.com/MediaArea/MediaInfoLib/issues", + } + s.add_runtime_dependency 'ffi', '~> 1' +end diff --git a/Source/Example/HowToUse_Dll.rb b/Source/Example/HowToUse_Dll.rb new file mode 100644 index 000000000..8bf4100d3 --- /dev/null +++ b/Source/Example/HowToUse_Dll.rb @@ -0,0 +1,114 @@ +## Copyright (c) MediaArea.net SARL. All Rights Reserved. + # + # Use of this source code is governed by a BSD-style license that can + # be found in the License.html file in the root of the source tree. + ## + +#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# +# Ruby example +# +# To make this example working, you must put the MediaInfoDLL.rb and +# example.ogg in the same folder, then run: +# ruby HowToUse_Dll.rb +# The MediaInfo library must reside in a standard library path (e.g. +# C:\Windows\System32\MediaInfo.dll on Windows), if this isn't the case set +# the LIBMEDIAINFO_PATH environment variable to the right library path. +#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + +require './MediaInfoDLL.rb' + +mi=MediaInfo::MediaInfo.new + +#Information about MediaInfo +puts "Info_Parameters" +puts mi.Option "Info_Parameters" + +puts "" +puts "Info_Codecs" +puts mi.Option "Info_Codecs" + +#An example of how to use the library +puts "" +puts "Open" +mi.Open "Example.ogg" + +puts "" +puts "Inform with Complete=false" +mi.Option "Complete" +puts mi.Inform + +puts "" +puts "Inform with Complete=true" +puts mi.Option "Complete", "1" +puts mi.Inform + +puts "" +puts "Custom Inform" +mi.Option "Inform", "General;Example : FileSize=%FileSize%" +puts mi.Inform + +puts "" +puts "Get with Stream=General and Parameter='FileSize'" +puts mi.Get :General, 0, "FileSize" + +puts "" +puts "Get with Stream=General and Parameter=46" +puts mi.Get :General, 0, 46 + +puts "" +puts "Count_Get with StreamKind=Stream_Audio" +puts mi.Count_Get :Audio + +puts "" +puts "Get with Stream=General and Parameter='AudioCount'" +puts mi.Get :General, 0, "AudioCount" + +puts "" +puts "Get with Stream=Audio and Parameter='StreamCount'" +puts mi.Get :Audio, 0, "StreamCount" + +puts "" +puts "Close" +mi.Close + +#By buffer example + +#Open example file for reading +File.open("Example.ogg", "r") do |file| + puts "" + puts "Open_Buffer_Init" + mi.Open_Buffer_Init file.size, 0 + + puts "" + puts "Parsing loop" + while true + buffer=file.read(7*188) + if buffer + #Send the buffer to MediaInfo + status=mi.Open_Buffer_Continue buffer, buffer.bytesize + + if (status & 0x08) #Bit3=Finished + break + end + + #Test if there is a MediaInfo request to go elsewhere + seek=mi.Open_Buffer_Continue_GoTo_Get + if seek != -1 + (2 ** 64) # equal to (uint64)-1 in C/C++ + file.seek seek #Seek the file + mi.Open_Buffer_Init file.size, file.tell #Inform MediaInfo we have seek + end + else + break + end + end + + puts "" + puts "Open_Buffer_Finalize" + mi.Open_Buffer_Finalize + + puts "" + puts "Get with Stream=General and Parameter='Format'" + puts mi.Get :General, 0, "Format" +end diff --git a/Source/MediaInfoDLL/MediaInfoDLL.rb b/Source/MediaInfoDLL/MediaInfoDLL.rb new file mode 100644 index 000000000..da4fe3000 --- /dev/null +++ b/Source/MediaInfoDLL/MediaInfoDLL.rb @@ -0,0 +1,329 @@ +require 'rbconfig' +require 'ffi' + +module MediaInfo extend(FFI::Library) + Stream = enum(:General, + :Video, + :Audio, + :Text, + :Other, + :Image, + :Menu, + :Max) + + Info = enum(:Name, + :Text, + :Measure, + :Options, + :Name_Text, + :Measure_Text, + :Info, + :HowTo, + :Max) + + InfoOptions = enum(:ShowInInform, + :Reserved, + :ShowInSupported, + :TypeOfValue, + :Max) + + FileOptions = enum(:Nothing, 0x00, + :NoRecursive, 0x01, + :CloseAll, 0x02, + :Max, 0x04) + + class MediaInfo extend(FFI::Library) + if ENV['LIBMEDIAINFO_PATH'] != nil + ffi_lib(ENV['LIBMEDIAINFO_PATH']) + else + if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/ + ffi_lib('MediaInfo.dll') + else # assume unix + ffi_lib('mediainfo') + end + end + + attach_function(:MediaInfo_New, [], :pointer) + attach_function(:MediaInfo_Delete, [:pointer], :void) + attach_function(:MediaInfo_Open, [:pointer, :pointer], :size_t) + attach_function(:MediaInfo_Open_Buffer_Init, [:pointer, :uint64, :uint64], :size_t) + attach_function(:MediaInfo_Open_Buffer_Continue, [:pointer, :buffer_in, :size_t], :size_t) + attach_function(:MediaInfo_Open_Buffer_Continue_GoTo_Get, [:pointer], :uint64) + attach_function(:MediaInfo_Open_Buffer_Finalize, [:pointer], :size_t) + attach_function(:MediaInfo_Open_NextPacket, [:pointer], :size_t) + attach_function(:MediaInfo_Close, [:pointer], :size_t) + attach_function(:MediaInfo_Inform, [:pointer], :pointer) + attach_function(:MediaInfo_Get, [:pointer, Stream, :size_t, :pointer, Info, Info], :pointer) + attach_function(:MediaInfo_Option, [:pointer, :pointer, :pointer], :pointer) + attach_function(:MediaInfo_State_Get, [:pointer], :size_t) + attach_function(:MediaInfo_Count_Get, [:pointer, Stream, :size_t], :size_t) + + def initialize() + # try to guess wchar_t size and endianness + if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/ + @wchar_bytesize = 2 + @wchar_type = :uint16 + @wchar_pack = 'S*' + @wchar_encoding = 'utf-16' + else # assume unix + @wchar_bytesize = 4 + @wchar_type = :uint32 + @wchar_pack = 'L*' + @wchar_encoding = 'utf-32' + end + + if [1].pack("I") == [1].pack("N") # big endian + @wchar_encoding += 'be' + else + @wchar_encoding += 'le' + end + + @ptr = New() + ObjectSpace.define_finalizer( self, self.class.finalize(@ptr)) + end + + def self.finalize(ptr) + proc { + MediaInfo_Delete(ptr) + } + end + + def New() + if @ptr != FFI::Pointer::NULL + Delete() + end + @ptr = MediaInfo_New() + end + + def Delete() + MediaInfo_Delete(@ptr) + @ptr = FFI::Pointer::NULL + end + + def Open(file) + FFI::MemoryPointer.new(@wchar_type, file.length + 1) do |p| + p.write_bytes(file.encode(@wchar_encoding)) + return MediaInfo_Open(@ptr, p) + end + end + + def Open_Buffer_Init(size, offset) + MediaInfo_Open_Buffer_Init(@ptr, size, offset) + end + + def Open_Buffer_Continue(buffer, size) + FFI::Buffer.new(:uint8, size) do |b| + b.write_bytes(buffer, 0, size) + return MediaInfo_Open_Buffer_Continue(@ptr, b, size) + end + end + + def Open_Buffer_Continue_GoTo_Get() + MediaInfo_Open_Buffer_Continue_GoTo_Get(@ptr) + end + + def Open_Buffer_Finalize() + MediaInfo_Open_Buffer_Finalize(@ptr) + end + + def Open_NextPacket() + MediaInfo_Open_NextPacket(@ptr) + end + + def Close() + MediaInfo_Close(@ptr) + end + + def Inform() + CWideStringPointerToString(MediaInfo_Inform(@ptr)) + end + + def Get(streamKind, streamNumber, parameter, infoKind = :Text, searchKind = :Name) + if parameter.is_a? Numeric + return CWideStringPointerToString(MediaInfo_GetI(@ptr, streamKind, streamNumber, parameter, infoKind)) + else + FFI::MemoryPointer.new(@wchar_type, parameter.length + 1) do |p| + p.write_bytes(parameter.encode(@wchar_encoding)) + return CWideStringPointerToString(MediaInfo_Get(@ptr, streamKind, streamNumber, p, infoKind, searchKind)) + end + end + end + + def Option(parameter, value = "") + FFI::MemoryPointer.new(@wchar_type, parameter.length + 1) do |pp| + pp.write_bytes(parameter.encode(@wchar_encoding)) + FFI::MemoryPointer.new(@wchar_type, value.length + 1) do |pv| + pv.write_bytes(value.encode(@wchar_encoding)) + return CWideStringPointerToString(MediaInfo_Option(@ptr, pp, pv)) + end + end + end + + def State_Get() + MediaInfo_State_Get(@ptr) + end + + def Count_Get(streamKind, streamNumber = -1) + MediaInfo_Count_Get(@ptr, streamKind, streamNumber) + end + + private :MediaInfo_New + private :MediaInfo_Delete + private :MediaInfo_Open + private :MediaInfo_Open_Buffer_Init + private :MediaInfo_Open_Buffer_Continue + private :MediaInfo_Open_Buffer_Continue_GoTo_Get + private :MediaInfo_Open_Buffer_Finalize + private :MediaInfo_Open_NextPacket + private :MediaInfo_Close + private :MediaInfo_Inform + private :MediaInfo_Get + private :MediaInfo_Option + private :MediaInfo_State_Get + private :MediaInfo_Count_Get + private + + def CWideStringPointerToString(ptr) + offset = 0 + array = [] + until (char = ptr.get(@wchar_type, offset)) == 0 + array.append(char) + offset += @wchar_bytesize + end + + codec = Encoding::Converter.new(@wchar_encoding, "utf-8", :undef => :replace) + codec.convert(array.pack(@wchar_pack)) + end + end + + class MediaInfoList extend(FFI::Library) + if ENV['LIBMEDIAINFO_PATH'] != nil + ffi_lib(ENV['LIBMEDIAINFO_PATH']) + else + if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/ + ffi_lib('MediaInfo.dll') + else # assume unix + ffi_lib('mediainfo') + end + end + + attach_function(:MediaInfoList_New, [], :pointer) + attach_function(:MediaInfoList_Delete, [:pointer], :void) + attach_function(:MediaInfoList_Open, [:pointer, :pointer, FileOptions], :size_t) + attach_function(:MediaInfoList_Close, [:pointer, :size_t], :size_t) + attach_function(:MediaInfoList_Inform, [:pointer, :size_t], :pointer) + attach_function(:MediaInfoList_Get, [:pointer, :size_t, Stream, :size_t, :pointer, Info, Info], :pointer) + attach_function(:MediaInfoList_Option, [:pointer, :pointer, :pointer], :pointer) + attach_function(:MediaInfoList_State_Get, [:pointer], :size_t) + attach_function(:MediaInfoList_Count_Get, [:pointer, :size_t, Stream, :size_t], :size_t) + + def initialize() + # try to guess wchar_t size and endianness + if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/ + @wchar_bytesize = 2 + @wchar_type = :uint16 + @wchar_pack = 'S*' + @wchar_encoding = 'utf-16' + else # assume unix + @wchar_bytesize = 4 + @wchar_type = :uint32 + @wchar_pack = 'L*' + @wchar_encoding = 'utf-32' + end + + if [1].pack("I") == [1].pack("N") # big endian + @wchar_encoding += 'be' + else + @wchar_encoding += 'le' + end + + @ptr = New() + ObjectSpace.define_finalizer( self, self.class.finalize(@ptr)) + end + + def self.finalize(ptr) + proc { + MediaInfoList_Delete(ptr) + } + end + + def New() + if @ptr != FFI::Pointer::NULL + Delete() + end + @ptr = MediaInfoList_New() + end + + def Delete() + MediaInfoList_Delete(@ptr) + @ptr = FFI::Pointer::NULL + end + + def Open(file, fileOptions = :Nothing) + FFI::MemoryPointer.new(@wchar_type, file.length + 1) do |p| + p.write_bytes(file.encode(@wchar_encoding)) + return MediaInfoList_Open(@ptr, p, fileOptions) + end + end + + def Close(filePos = -1) + MediaInfoList_Close(@ptr, filePos) + end + + def Inform(filePos = -1) + CWideStringPointerToString(MediaInfoList_Inform(@ptr, filePos)) + end + + def Get(filePos, streamKind, streamNumber, parameter, infoKind = :Text, searchKind = :Name) + if parameter.is_a? Numeric + return CWideStringPointerToString(MediaInfoList_GetI(@ptr, filePos, streamKind, streamNumber, parameter, infoKind)) + else + FFI::MemoryPointer.new(@wchar_type, parameter.length + 1) do |p| + p.write_bytes(parameter.encode(@wchar_encoding)) + return CWideStringPointerToString(MediaInfoList_Get(@ptr, filePos, streamKind, streamNumber, p, infoKind, searchKind)) + end + end + end + + def Option(parameter, value = "") + FFI::MemoryPointer.new(@wchar_type, parameter.length + 1) do |pp| + pp.write_bytes(parameter.encode(@wchar_encoding)) + FFI::MemoryPointer.new(@wchar_type, value.length + 1) do |pv| + pv.write_bytes(value.encode(@wchar_encoding)) + return CWideStringPointerToString(MediaInfoList_Option(@ptr, pp, pv)) + end + end + end + + def State_Get() + MediaInfoList_State_Get(@ptr) + end + + def Count_Get(filePos, streamKind, streamNumber = -1) + MediaInfoList_Count_Get(@ptr, filePos, streamKind, streamNumber) + end + + private :MediaInfoList_New + private :MediaInfoList_Delete + private :MediaInfoList_Open + private :MediaInfoList_Close + private :MediaInfoList_Inform + private :MediaInfoList_Get + private :MediaInfoList_Option + private :MediaInfoList_State_Get + private :MediaInfoList_Count_Get + private + + def CWideStringPointerToString(ptr) + offset = 0 + array = [] + until (char = ptr.get(@wchar_type, offset)) == 0 + array.append(char) + offset += @wchar_bytesize + end + + codec = Encoding::Converter.new(@wchar_encoding, "utf-8", :undef => :replace) + codec.convert(array.pack(@wchar_pack)) + end + end +end