Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
lamt/id3v1.lua
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
248 lines (211 sloc)
7.09 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --[[ | |
| LOMP ( Lua Open Music Player ) | |
| Copyright (C) 2007- daurnimator | |
| This program is free software: you can redistribute it and/or modify it under the terms of version 3 of the GNU General Public License as published by the Free Software Foundation. | |
| 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, see <http://www.gnu.org/licenses/>. | |
| ]] | |
| require "general" | |
| local ipairs , pairs , pcall , select , require , tonumber , tostring , type = ipairs , pairs , pcall , select , require , tonumber , tostring , type | |
| local ioopen = io.open | |
| module ( "lomp.fileinfo.id3v1" , package.see ( lomp ) ) | |
| local iconv = require "iconv" | |
| local genreindex = require ( select ( 1 , ... ):match ( "(.*%.)[^.]+$" ) .. "genrelist" ) | |
| local Locale = "ISO-8859-1" | |
| local toid3 = iconv.new ( Locale , "UTF-8" ) | |
| _NAME = "ID3v1 tag utils" | |
| local speedindex = { | |
| [1] = "slow" , | |
| [2] = "medium" , | |
| [3] = "fast" , | |
| [4] = "hardcore" | |
| } | |
| local function readstring ( str ) | |
| str = str:gsub ( "[%s%z]*$" , "" ) | |
| if #str == 0 then return nil end | |
| return str:utf8 ( Locale ) | |
| end | |
| function find ( fd ) | |
| fd:seek ( "end" , -128 ) -- Look at end of file | |
| if fd:read ( 3 ) == "TAG" then | |
| return fd:seek ( "end" , -128 ) | |
| else | |
| return false | |
| end | |
| end | |
| function info ( fd , offset ) | |
| -- offset should always be (size of file-128) | |
| if offset then fd:seek ( "set" , offset ) end | |
| if fd:read ( 3 ) == "TAG" then | |
| local tags = { } | |
| tags.title = { readstring ( fd:read( 30 ) ) } | |
| tags.artist = { readstring ( fd:read ( 30 ) ) } | |
| tags.album = { readstring ( fd:read ( 30 ) ) } | |
| tags.date = { readstring ( fd:read ( 4 ) ) } | |
| do -- ID3v1 vs ID3v1.1 | |
| local a = fd:read ( 28 ) | |
| local b = fd:read ( 1 ) | |
| if b == "\0" then -- Check if comment is 28 or 30 characters | |
| -- Get up to 28 character comment | |
| fd:seek ( "cur" , -29 ) | |
| tags.comment = { readstring ( a , 28 ) } | |
| -- Get track number | |
| local track = fd:read ( 1 ):byte ( ) | |
| if track ~= 0 then tags.tracknumber = { track } end | |
| else -- Is ID3v1, could have a 30 character comment tag | |
| tags.comment = { readstring ( a .. b .. fd:read ( 1 ) ) } | |
| end | |
| end | |
| tags.genre = { genreindex [ tonumber ( fd:read ( 1 ):byte( ) ) ] } | |
| -- Check for extended tags (Note: these are damn rare, worthwhile supporting them??) | |
| fd:seek ( "end" , -355 ) | |
| if fd:read ( 4 ) == "TAG+" then | |
| tags.title [ 1 ] = tags.title[1] .. readstring ( fd:read ( 60 ) ) | |
| tags.artist [ 1 ] = tags.artist[1] .. readstring ( fd:read ( 60 ) ) | |
| tags.album [ 1 ] = tags.album[1] .. readstring ( fd:read ( 60 ) ) | |
| tags.speed = { speedindex [ tonumber ( fd:read ( 1 ):byte ( ) ) ] } | |
| tags.genre [ 2 ] = readstring ( fd:read ( 30 ) ) | |
| do | |
| local start = readstring ( fd:read ( 30 ) ) | |
| if #start == 6 and start:sub ( 4 , 4 ) == ":" then | |
| tags["start-time"] = { tostring ( tonumber ( start:sub ( 1 , 3 ) ) * 60 + start:sub ( 5 , 6 ) ) } | |
| end | |
| local fin = readstring ( fd:read ( 30 ) ) | |
| if #fin == 6 and fin:sub ( 4 , 4 ) == ":" then | |
| tags["end-time"] = { tostring ( tonumber ( fin:sub ( 1 , 3 ) ) * 60 + fin:sub ( 5 , 6 ) ) } | |
| end | |
| end | |
| end | |
| return tags , { } | |
| else | |
| -- File doesn't have an ID3v1 Tag | |
| return false | |
| end | |
| end | |
| local function twodigityear ( capture ) | |
| if tonumber ( capture ) < 30 then -- Break on 30s | |
| return "20"..capture | |
| else | |
| return "19" .. capture | |
| end | |
| end | |
| local function guessyear ( datestring ) | |
| local patterns = { | |
| [".*%f[%d](%d%d%d%d)%f[%D].*"] = { matches = 1 , replace = "%1" }, -- MAGICAL FRONTIER PATTERN (undocumented) | |
| ["^%W*(%d%d)%W*$"] = { matches = 1 , replace = twodigityear }, -- Eg: 70 or '70 | |
| ["^%s*%d%d?%s*[/-%s]%s*%d%d?%s*[/-%s]%W*(%d%d)%s*$"] = { matches = 1 , replace = twodigityear }, -- Eg: 20/4/87 | |
| [".*Year%W*(%d%d)%W.-"] = { matches = 1 , replace = twodigityear }, -- Eg: Month: October, Year: 69 | |
| [".*%W(%d%d)%W*$"] = { matches = 1 , replace = twodigityear }, -- Eg: Concert of '97. | |
| } | |
| for k , v in pairs ( patterns ) do | |
| local s , m = datestring:gsub ( k , v.replace ) | |
| if m == v.matches then return s end | |
| end | |
| return false | |
| end | |
| local function makestring ( str , tolength ) | |
| str = toid3:iconv ( str ) | |
| str = str:sub ( 1 , tolength ) | |
| return str .. ("\0"):rep ( tolength-#str ) | |
| end | |
| function generatetag ( tags ) | |
| local title , artist , album, year , comment , track , genre | |
| -- Title | |
| if type ( tags.title ) == "table" then | |
| title = tags.title | |
| else title = { "" } | |
| end | |
| title = makestring ( title [ 1 ] , 30 ) | |
| do -- Artist | |
| local t | |
| if type ( tags.artist ) == "table" then | |
| t= tags.artist | |
| else t = { "" } | |
| end | |
| artist = t [ 1 ]:sub ( 1 , 30 ) | |
| for i=2 , #t-1 do | |
| if ( #artist + 3 + #t [ i ] ) > 30 then break end | |
| artist = artist .. " & " .. t [ i ] | |
| end | |
| artist = makestring ( artist , 30 ) | |
| end | |
| -- Album | |
| if type ( tags.album ) == "table" then | |
| album = tags.album | |
| else album = { "" } | |
| end | |
| album = makestring ( album [ 1 ] , 30 ) | |
| -- Year | |
| if type ( tags.date ) == "table" then | |
| year = tags.date | |
| else year = { "" } | |
| end | |
| year = makestring ( ( guessyear ( year [ 1 ] ) or "" ) , 4 ) | |
| -- Comment | |
| if tags.comment and #tags.comment [ 1 ] <= 28 then | |
| comment = tags.comment [ 1 ] | |
| else | |
| comment = "" | |
| end | |
| -- No one likes 28 character comments | |
| comment = makestring ( comment , 28 ) | |
| -- Track | |
| if type ( tags.track ) == "table" then | |
| track = tags.tracknumber | |
| else track = { "" } | |
| end | |
| track = ( tonumber ( track [ 1 ] ) or 0 ):char ( ) | |
| -- Genre | |
| if type ( tags.genre ) == "table" then | |
| genre = toid3:iconv ( tags.genre [ 1 ] ) | |
| else | |
| genre = "" | |
| end | |
| do | |
| local t = 12 -- "Other" | |
| for i , v in ipairs ( genreindex ) do | |
| if genre:lower ( ):find ( v:lower ( ):gsub ( "%W" , "." ) ) then | |
| t = i | |
| break | |
| end | |
| end | |
| genre = t:char ( ) | |
| end | |
| local datadiscarded = false | |
| for k , v in pairs ( tags ) do | |
| if k == "title" or k == "artist" or k == "album" or k == "date" or k == "tracknumber" or k == "genre" then | |
| if v [ 2 ] then | |
| datadiscarded = true | |
| break | |
| end | |
| else | |
| datadiscarded = true | |
| break | |
| end | |
| end | |
| local tag = "TAG" .. title .. artist .. album .. year .. comment .. "\0" .. track .. genre -- String format doesn't like \0. --string.format ( "TAG%s%s%s%s%s\0%s%s" , title , artist , album , year , comment , track , genre) | |
| return tag , datadiscarded | |
| end | |
| function edit ( path , tags , inherit ) | |
| local fd , err = ioopen ( path , "rb+" ) | |
| if not fd then return ferror ( err , 3 ) end | |
| if inherit then | |
| local currenttags= info ( fd ) -- inherit from current data | |
| for k , v in pairs ( currenttags ) do | |
| for i , v in ipairs ( v ) do | |
| tags [ k ] [ #tags [ k ] + 1 ] = v | |
| end | |
| end | |
| end | |
| local id3 = generatetag ( tags ) | |
| if #id3 ~= 128 then return ferror ( "Unknown error" , 3 ) end | |
| -- Check if file already has an ID3 tag | |
| local starttag = find ( fd ) | |
| if not starttag then -- If file has no id3v1 tag | |
| fd:seek ( "end" ) -- make tag at end of file | |
| end | |
| local ok , err = fd:write ( id3 ) | |
| if not ok then return ok , err end | |
| fd:flush ( ) | |
| return | |
| end |