Advertisement
uriid1

Parse WAVE PCM soundfile | lua, luajit, luvit

Sep 2nd, 2022 (edited)
1,024
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 6.20 KB | None | 0 0
  1. -- ####--------------------------------####
  2. -- #--# Author:   by uriid1            #--#
  3. -- #--# License:  GNU GPLv3            #--#
  4. -- #--# Telegram: @main_moderator      #--#
  5. -- #--# E-mail:   appdurov@gmail.com   #--#
  6. -- ####--------------------------------####
  7.  
  8. -- Описание .wav
  9. -- http://soundfile.sapp.org/doc/WaveFormat/
  10.  
  11. -- Эта функция взята с
  12. -- https://stackoverflow.com/questions/5241799/lua-dealing-with-non-ascii-byte-streams-byteorder-change
  13. -- И изменена под этот скрипт
  14. function bytes2int(str, endian)
  15.  
  16.     -- Получаем все байты из строки
  17.     -- в таблицу t
  18.     local t = {
  19.         str:byte(1, -1)
  20.     }
  21.  
  22.     -- Если это big-endian
  23.     -- то делаем reverse таблицы
  24.     if endian == "big" then
  25.         local tt = {}
  26.         for i = #t, 1, -1 do
  27.             table.insert(tt, t[i])
  28.         end
  29.         t = tt
  30.     end
  31.  
  32.     local n = 0
  33.     for k = 1, #t do
  34.         n = n + t[k] * 2^( (k-1)*8 )
  35.     end
  36.    
  37.     return math.floor(n)
  38. end
  39.  
  40. function int2bytes(num, endian, byte_count)
  41.  
  42.     local res = {}
  43.     local n = math.ceil(select(2, math.frexp(num)) / 8) -- number of bytes to be used.
  44.  
  45.     for k = n, 1, -1 do -- 256 = 2^8 bits per char.
  46.         local mul = 2^(8*(k-1))
  47.         res[k] = math.floor(num / mul)
  48.         num = num - res[k] * mul
  49.     end
  50.  
  51.     if endian == "big" then
  52.         local t = {}
  53.         for k = 1,n do
  54.             t[k] = res[n-k+1]
  55.         end
  56.         res = t
  57.     end
  58.  
  59.     while #res ~= byte_count do
  60.         table.insert(res, 0)
  61.     end
  62.  
  63.     return string.char(table.unpack(res))
  64. end
  65.  
  66. -- Парсим заголовок и содержимое
  67. local function parse_wave(path)
  68.     local wav = {}
  69.     local file = io.open(path, 'rb')
  70.     wav.chunkId       = file:read(4)
  71.     wav.chunkSize     = bytes2int(file:read(4), 'little')
  72.  
  73.     wav.format        = file:read(4)
  74.     wav.subchunk1Id   = file:read(4)
  75.  
  76.     wav.subchunk1Size = bytes2int(file:read(4), 'little')
  77.     wav.audioFormat   = bytes2int(file:read(2), 'little')
  78.     wav.numChannels   = bytes2int(file:read(2), 'little')
  79.     wav.sampleRate    = bytes2int(file:read(4), 'little')
  80.     wav.byteRate      = bytes2int(file:read(4), 'little')
  81.     wav.blockAlign    = bytes2int(file:read(2), 'little')
  82.     wav.bitsPerSample = bytes2int(file:read(2), 'little')
  83.  
  84.     wav.subchunk2Id   = file:read(4)
  85.    
  86.     -- d = 100
  87.     -- a = 97
  88.     -- t = 116
  89.  
  90.     -- Некоторые конвертеры добавляют свои данные -
  91.     -- до блока 'date', поэтому пропускаем эти данные и ищем 'date'
  92.     -- пример ffmpeg -i example.mp3 -f wav example.wav
  93.  
  94.     -- Запоминаем сколько байт пропустили
  95.     -- Это хак иногда нужно пропустить два байта
  96.     wav.byteOffset = 0
  97.  
  98.     wav.undefendedData = ''
  99.  
  100.     if wav.subchunk2Id ~= 'data' then
  101.        
  102.         -- Помещаем полученные байты в таблицу
  103.         local b = {}
  104.         string.gsub(wav.subchunk2Id, '(.)', function(s)
  105.             table.insert(b, s)
  106.         end)
  107.  
  108.         if #b < 4 then
  109.             for i = 1, 4 do
  110.                 b[i] = b[i] or 0
  111.             end
  112.         end
  113.  
  114.         -- Хак для некоторых жопных .wav
  115.         if b[3] == 'd' and b[4] == 'a' then
  116.             wav.byteOffset = wav.byteOffset + 2
  117.         end
  118.  
  119.         -- Ищем слово data, со смещением в 2 байта
  120.         while true do
  121.             local c, d = file:read(2):byte(1, -1)
  122.             b[1], b[2] = b[3], b[4]
  123.             b[3] = string.char(c)
  124.             b[4] = string.char(d)
  125.            
  126.             if table.concat(b) == 'data' then
  127.                 wav.subchunk2Id = 'data'
  128.                 break
  129.             end
  130.  
  131.             -- Неизвестные данные тоже записываем
  132.             wav.undefendedData = wav.undefendedData..b[3]..b[4]
  133.         end
  134.     end
  135.  
  136.     wav.subchunk2Size = bytes2int(file:read(4), 'little')
  137.     wav.data = file:read(wav.subchunk2Size)
  138.  
  139.     file:close()
  140.     return wav
  141. end
  142.  
  143. -- Записываем Wav файл
  144. local function write_wave(path, wav_t)
  145.     local file = io.open(path, 'wb')
  146.     local res = ''
  147.  
  148.     res = res
  149.     .. wav_t.chunkId
  150.     .. int2bytes(wav_t.chunkSize, 'little', 4)
  151.     .. wav_t.format
  152.     .. wav_t.subchunk1Id
  153.  
  154.     .. int2bytes(wav_t.subchunk1Size, 'little', 4)
  155.     .. int2bytes(wav_t.audioFormat,   'little', 2)
  156.     .. int2bytes(wav_t.numChannels,   'little', 2)
  157.     .. int2bytes(wav_t.sampleRate,    'little', 4)
  158.     .. int2bytes(wav_t.byteRate,      'little', 4)
  159.     .. int2bytes(wav_t.blockAlign,    'little', 2)
  160.     .. int2bytes(wav_t.bitsPerSample, 'little', 2 + wav_t.byteOffset)
  161.     .. wav_t.subchunk2Id
  162.     .. int2bytes(wav_t.subchunk2Size, 'little', 4)
  163.     .. wav_t.data
  164.  
  165.     file:write(res)
  166.     file:close()
  167. end
  168.  
  169.  
  170. ------------------
  171. -- Парсим
  172. ------------------
  173. local wav = parse_wave(arg[1])
  174.  
  175. -- Можно засейвить
  176. -- write_wave('result.wav', wav)
  177.  
  178. print('RIFF chunk')
  179. print('\tchunkId:',     wav.chunkId)
  180. print('\tchunkSize:',     wav.chunkSize)
  181. print('\n')
  182.  
  183. print('fmt sub-chunk')
  184. print('\tformat:\t',      wav.format)
  185. print('\tsubchunk1Id:',   wav.subchunk1Id)
  186. print('\tsubchunk1Size:', wav.subchunk1Size)
  187. print('\taudioFormat:',   wav.audioFormat)
  188. print('\tnumChannels:',   wav.numChannels)
  189. print('\tsampleRate:',    wav.sampleRate)
  190. print('\tbyteRate:',      wav.byteRate)
  191. print('\tblockAlign:',    wav.blockAlign)
  192. print('\tbitsPerSample:', wav.bitsPerSample)
  193. print('\n')
  194.  
  195. print('data sub-chunk')
  196. print('\tsubchunk2Id:',   wav.subchunk2Id)
  197. print('\tsubchunk2Size:', wav.subchunk2Size)
  198. print('\n')
  199.  
  200. -- Посчитаем длительность воспроизведения в секундах
  201. local seconds = 1.0 * wav.subchunk2Size / (wav.bitsPerSample / 8) / wav.numChannels / wav.sampleRate
  202. local min = math.floor(seconds) / 60
  203. local sec = math.floor(60 * (min - math.floor(min)))
  204.  
  205. local str_min = string.format('%02d', math.floor(min))
  206. local str_sec = string.format('%02d', sec)
  207. print("Duration: " .. str_min .. ':' .. str_sec)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement