Automated conversion scripts:
makemp2.php
makem1v.php
makegop.php
makehdr.php
makedpg.sh


DPG is a simple container format for mpeg video and audio.
The header is big endian, and the data is just a concatenation of raw mpeg video and either mpeg audio or GSM audio.

The DPG format is a video and audio container used by the popular
Moonshell homebrew program on the Nintendo DS.

There are currently (December 2006) three versions of the DPG format,
identified by the initial four bytes of in the DPG file, namely DPG0,
DPG1 and DPG2.

[DPG file structure]
Each DPG file consists of three (DPG0,DPG1) or four (DPG2) sections: a
header, the audio stream, the video stream and for the DPG2 format an
additional GOP (Group Of Pictures) list used for seeking during
playback. All these sections are concatenated in the above mentioned
order when creating the DPG file.

[Header]
The first section of the DPG file is the header with the following
fields: (all fields are read by Moonshell 1.5 as a 32 bit unsigned
number)

Offset                  Description
0     DPG[012]          Identifier. One of the strings: DPG0 (0x30475044),
                        DPG1 (0x31475044) or DPG2 (0x32475044).
4     DPG[012]          Number of frames in video.
8     DPG[012]          Frames per second (FPS). Fixed point 8.8 format, i.e.
                        25 fps is multiplied by 100h (FPS<<8) to 6400.
12    DPG[012]          Sampling rate of audio data (e.g. 32000)
16    DPG[012]          Number of audio channels. If MP2 audio, this is zero.
20    DPG[012]          Offset in file to audio stream
24    DPG[012]          Size of audio stream.
28    DPG[012]          Offset in file to video stream
32    DPG[012]          Size of video stream.
36    DPG[1]            Pixel format type

36    DPG[2]            Offset in file to GOP list.
40    DPG[2]            Size of GOP list.
44    DPG[2]            Pixel format type

Note: dpgtools 1.2 reads all values except the identifier as a signed
32 bit value. Moonshell 1.5, on the other hand, reads them as unsigned
32 bit values. This should not pose any problem, at least not until we
reach 2GB files.


[Pixel Formats]
DPG0 only supports the RGB24 pixel format. DPG1 and DPG2 know the
following pixel formats,

Type     Description
0        RGB15
1        RGB18
2        RGB21
3        RGB24


[GOP List]
The Group Of Pictures (GOP) list of a DPG2 file is the last section of
a DPG file. It consists of an array of a "FrameIndex" and "Offset"
pair, both are read as an unsigned 32 bit value from within Moonshell.

The use of the GOP list, within Moonshell 1.5, is when requesting a
jump to a certain frame. In the case when a GOP list exists, it is
searched in order to find the frame within the GOP list lower than or
equal to the requested frame. (The search in Moonshell requires the
GOP list to be ordered. Also a trivial optimization was omitted,
namely to break out of the search loop once the GOP frame number is
larger than the requested one.)

So, to get clean seeks, I would put I-frames from the MPEG stream in
the GOP list.

[Audio Format]
The audio format can be one of two types: GSM or MP2. The type used in
a DPG file is distinguished by the number of audio channels. If audio
channels is zero, an MP2 stream is assumed, otherwise the audio is GSM.

Some useful commands:

Audio conversion doesn't seem to work correctly in mencoder, so I used sox and twolame.

mplayer -ao pcm:fast -vo null file.avi # extract the audio
sox audiodump.wav -r 32000 out.wav # Transform it into 32kHz, to save some space
twolame -b 128 out.wav out.mp2 # Create an Mpeg-1 Layer-II file

dpgconv calculates the video stuff and transcodes it sufficiently well.

Patch the header file, and concatenate the results together.

mplayer -aid 0 source.mkv -endpos 1:35 -vo null -ao pcm:fast sox audiodump.wav -r 32000 audio.wav twolame -b 128 audio.wav audio.mp2

mencoder source.mkv -endpos 1:35 -v -ofps 16 -sws 9 -vf format=rgb24,scale=256:146:::3 -nosound -ovc lavc -lavcopts vcodec=mpeg1video:vstrict=-2:mbd=2:trell:cbp:mv0:cmp=6:subcmp=6:precmp=6:dia=3:predia=3:last_pred=3:vbitrate=256 -o video.mpg -of rawvideo


mencoder source.avi -o output.mp2 -of rawaudio -srate 32000 -ovc copy -oac lavc -lavcopts acodec=mp2:abitrate=128

mencoder source.avi -o output.m1v -of rawvideo -ofps 15 -vf scale=256:192:0:0:1.00:0.00 -ovc lavc -oac copy -lavcopts vcodec=mpeg1video:vstrict=-1:vbitrate=384:vrc_buf_size=327:vrc_maxrate=512:keyint=15

mpeg_stat -offsets output.off output.m1v

mpeg_stat will give you the frame count.

cat output.hdr output.mp2 output.m1v output.gop > output.dpg

A PHP script to turn output.off into a gop list suitable for DPG2

#!/usr/bin/env php
<?php

$stat = fopen("output.off","r");
$gop = fopen("output.gop", "w");

$frame = 0;
while ($line = fgets($stat, 4096)) {
  $stuff = explode(" ", $line);
  switch ($stuff[0]) {
    case "picture":
      $frame++;
      break;
    case "gop":
      if ($frame) {
        $point = (((int) $stuff[1]) / 8 - 140);
        printf("%u: %u\n", $frame, $point);
        fwrite($gop, pack('VV', $frame, $point), 8);
      }
      break;
    default:
      break;
  }
}

?>

A PHP script to create a DPG2-compatible binary header

#!/usr/bin/env php
<?php

# Make header
$asize = filesize("output.mp2");
$vsize = filesize("output.m1v");
$gsize = filesize("output.gop");

$astart = 36 + 12;
$vstart = $astart + $asize;
$gstart = $vstart + $vsize;

$frames = 2737;
$fps = 15;
$hz = 32000;

$hdr = sprintf("DPG2%s", pack('lnnlllllllll', $frames, $fps, 0, $hz, 0, $astart, $asize, $vstart, $vsize, $gstart, $gsize, 3));

file_put_contents("output.hdr", $hdr);

?>

Some command line options, stolen shamelessly from BatchDPG v1.55's mencoder invocation. The trick here is that avisynth resamples to the target framerate, then lies and says it's 60fps (mpeg spec). Moonshell ignores that, and uses the framerate in the DPG header so this works. For linux, it's usually easier to add vstrict=-1 to the lavcopts to turn off bitching about out-of-spec target framerates.

Ultra pass 1:
C:\DOCUME~1\ADMINI~1\Desktop\BATCHD~1.55\Bin\mencoder.exe "C:\dpg\tmp\Rozen Maiden 01.avi.avs" -o "C:\dpg\tmp\Rozen Maiden 01.avi.m1v" -nosound -passlogfile "C:\dpg\tmp\Rozen Maiden 01.avi.log" -ovc lavc -lavcopts vcodec=mpeg1video:keyint=60:vrc_buf_size=327:vrc_maxrate=3072:vbitrate=1536:vpass=1:mbd=2:trell:cbp:mv0:vmax_b_frames=2:cmp=6:subcmp=6:precmp=6:dia=4:predia=4:vb_strategy=2:bidir_refine=4:mv0_threshold=0:last_pred=3:preme=2:intra_matrix=8,9,12,22,26,27,29,34,9,10,14,26,27,29,34,37,12,14,18,27,29,34,37,38,22,26,27,31,36,37,38,40,26,27,29,36,39,38,40,48,27,29,34,37,38,40,48,58,29,34,37,38,40,48,58,69,34,37,38,40,48,58,69,79:inter_matrix=16,18,20,22,24,26,28,30,18,20,22,24,26,28,30,32,20,22,24,26,28,30,32,34,22,24,26,30,32,32,34,36,24,26,28,32,34,34,36,38,26,28,30,32,34,36,38,40,28,30,32,34,36,38,42,42,30,32,34,36,38,40,42,44 -of rawvideo

Ultra pass 2:
C:\DOCUME~1\ADMINI~1\Desktop\BATCHD~1.55\Bin\mencoder.exe "C:\dpg\tmp\Rozen Maiden 01.avi.avs" -o "C:\dpg\tmp\Rozen Maiden 01.avi.m1v" -nosound -passlogfile "C:\dpg\tmp\Rozen Maiden 01.avi.log" -ovc lavc -lavcopts vcodec=mpeg1video:keyint=60:vrc_buf_size=327:vrc_maxrate=3072:vbitrate=1536:vpass=2:mbd=2:trell:cbp:mv0:vmax_b_frames=2:cmp=6:subcmp=6:precmp=6:dia=4:predia=4:vb_strategy=2:bidir_refine=4:mv0_threshold=0:last_pred=3:preme=2:intra_matrix=8,9,12,22,26,27,29,34,9,10,14,26,27,29,34,37,12,14,18,27,29,34,37,38,22,26,27,31,36,37,38,40,26,27,29,36,39,38,40,48,27,29,34,37,38,40,48,58,29,34,37,38,40,48,58,69,34,37,38,40,48,58,69,79:inter_matrix=16,18,20,22,24,26,28,30,18,20,22,24,26,28,30,32,20,22,24,26,28,30,32,34,22,24,26,30,32,32,34,36,24,26,28,32,34,34,36,38,26,28,30,32,34,36,38,40,28,30,32,34,36,38,42,42,30,32,34,36,38,40,42,44 -of rawvideo