I recently found a bin of SD cards from a JVC camcorder I used years ago for school projects. I wanted to import the videos into DigiKam to catalog them with the other photos and videos I've taken with other cameras. When trying to complete this I quickly ran into an issue. The camcorder recorded videos into a combination of MOD and MOI files, which most software doesn't recognize.
The SD cards contained directories structured like this.
$ ls Card\ 1/
DCIM EXTMOV PRIVATE SD_VIDEO
DCIM
contained normal JPEG images, and videos were stored in
SD_VIDEO
.
The videos were then grouped into sub-directories.
$ ls Card\ 1/SD_VIDEO/
MGR_INFO PRG001 PRG002
Finally the videos themselves were stored in a directory that looked roughly like this.
$ ls Card\ 1/SD_VIDEO/PRG002/
MOV002.MOD MOV003.MOD MOV007.MOD MOV008.MOD MOV009.MOD MOV00A.MOD
MOV002.MOI MOV003.MOI MOV007.MOI MOV008.MOI MOV009.MOI MOV00A.MOI
But what are these files and what do they contain?
We can see immediately from looking at the file sizes that the MOD
files probably contain the video, and that the MOI
files are most
likely some sort of metadata.
$ cd Card\ 1/SD_VIDEO/PRG002
$ ls -lh
total 592M
-rw-------. 1 dante dante 684K Sep 6 2009 MOV002.MOD
-rw-------. 1 dante dante 275 Sep 6 2009 MOV002.MOI
-rw-------. 1 dante dante 1.2M Sep 6 2009 MOV003.MOD
-rw-------. 1 dante dante 278 Sep 6 2009 MOV003.MOI
-rw-------. 1 dante dante 88M Sep 6 2009 MOV007.MOD
-rw-------. 1 dante dante 1.1K Sep 6 2009 MOV007.MOI
-rw-------. 1 dante dante 7.5M Sep 6 2009 MOV008.MOD
-rw-------. 1 dante dante 345 Sep 6 2009 MOV008.MOI
-rw-------. 1 dante dante 3.6M Sep 6 2009 MOV009.MOD
-rw-------. 1 dante dante 302 Sep 6 2009 MOV009.MOI
-rw-------. 1 dante dante 217M Sep 6 2009 MOV00A.MOD
-rw-------. 1 dante dante 2.4K Sep 6 2009 MOV00A.MOI
Using the file
command, we can get some information about what kind
of video is might contain.
$ file MOV002.MOD
MOV002.MOD: MPEG sequence, v2, program multiplex
We can check the file using ffprobe
to get more details.
$ ffprobe MOV002.MOD
[...]
Input #0, mpeg, from 'MOV002.MOD':
Duration: 00:00:01.02, start: 0.215278, bitrate: 5472 kb/s
Stream #0:0[0x1e0]: Video: mpeg2video (Main), yuv420p(tv, smpte170m, top first), 720x480 [SAR 32:27 DAR 16:9], 29.97 fps, 29.97 tbr, 90k tbn
Side data:
cpb: bitrate max/min/avg: 7700000/0/0 buffer size: 1835008 vbv_delay: N/A
Stream #0:1[0x80]: Audio: ac3, 48000 Hz, stereo, fltp, 256 kb/s
Nice! It looks like we're dealing with a combination of mpeg2
video
and ac3
audio.
We should be able to run this through ffmpeg
and get a usable file
out of it. I've decided to convert the videos to MP4s for the greatest
compatibility with media manager software. Since MP4s are allowed to
contain MPEG2 video, we will be able to use the -vcodec copy
option
to leave the video untouched.
Unfortunately ac3
audio isn't really supported by the MP4 container.
It could probably be done but it won't be well supported in most video
players. For this reason we'll be transcoding the audio to AAC
using
-acodec aac
.
We only have one problem remaining. The file metadata, like the day the video was recorded, is stored in the MOI file. We want to preserve that so all the videos can be ordered correctly in DigiKam.
Fortunately there's a stub article on Wikipedia that describes some of
the contents of the MOI
files, including the date and time
information. Now all we have to do is parse it and add that
information into the MP4 when we convert it.
We can extract the metadata from the MOI
file and then re-insert it
as a creation_time
metadata field in the MP4. We can accomplish this
using the -metadata creation_time
flag on ffmpeg
.
It will also be a good idea to modify the file's timestamp in case an
application can't read the metadata correctly. Unfortunately I wasn't
able to find a reliable way to modify a file's creation timestamp, but
we can modify its last modified and last accessed timestamps using
touch
.
Now we just need to write a small script to automate this process and we should be good to go!
This is what I came up with using Ruby.
#!/usr/bin/env ruby
# frozen_string_literal: true
# https://en.wikipedia.org/wiki/MOI_(file_format)
def parse_moi(filename)
data = File.read(filename)
version = data[0..1]
size = data[2..5].unpack1('L>')
year = data[6..7].unpack1('S>')
month = data[8].unpack1('C')
day = data[9].unpack1('C')
hour = data[0xa].unpack1('C')
minutes = data[0xb].unpack1('C')
milliseconds = data[0xc..0xd].unpack1('S>')
seconds = milliseconds / 1000
{
version: version,
size: size,
year: year.to_s,
month: month.to_s.rjust(2, '0'),
day: day.to_s.rjust(2, '0'),
hour: hour.to_s.rjust(2, '0'),
minutes: minutes.to_s.rjust(2, '0'),
seconds: seconds.to_s.rjust(2, '0')
}
end
if ARGV.empty? || ['-h', '--help'].include?(ARGV[0])
puts 'usage: convert.rb <directory>...'
puts ' Convert one or more directories full of MOI and MOD files into MP4 files with correct metadata'
return
end
ARGV.each do |dir|
moi_files = Dir[File.join(dir, '*.MOI')]
moi_files.each do |moi|
data = parse_moi(moi)
puts "#{moi}: #{data}"
mod = moi.sub(/MOI$/, 'MOD')
raise "Video file doesn't exist: #{mod}" unless File.exist?(mod)
mp4 = moi.sub(/MOI$/, 'mp4')
if File.exist?(mp4)
puts "#{mp4} already exists, skipping"
next
end
ffmpeg = "ffmpeg -i \"#{mod}\" -vcodec copy -acodec aac -metadata \"creation_time=#{data[:year]}-#{data[:month]}-#{data[:day]} #{data[:hour]}:#{data[:minutes]}:#{data[:seconds]}Z\" \"#{mp4}\""
touch = "TZ=UTC touch -t #{data[:year]}#{data[:month]}#{data[:day]}#{data[:hour]}#{data[:minutes]}.#{data[:seconds]} \"#{mp4}\""
system(ffmpeg)
system(touch)
end
end
All we have to do now is run the script with the directories containing the old camcorder videos and we'll be left with newly converted and widely compatible MP4s, ready to be watched!