Skip to content

Instantly share code, notes, and snippets.

@tociyuki tociyuki/cube2jpeg.pl
Last active May 8, 2017

Embed
What would you like to do?
extract six faces from a QuickTime Cubic VR mov file
#!/usr/bin/env perl
#
# cube2jpeg.pl - extract six jpeg faces from a QuickTime Cubic VR mov file.
#
# $ perl cube2jpeg.pl DSCN3019.mov
# $ ls DSCN3019
# 0.jpg 1.jpg 2.jpg 3.jpg 4.jpg 5.jpg
#
# Copyright 2017 by MIZUTANI Tociyuki
# LICENSE: same as perl itself
use strict;
use warnings;
use English qw(-no_match_vars);
use Carp qw(carp croak);
sub run {
@ARGV or die "usage: $0 a.mov\n";
cube2jpeg(shift @ARGV);
}
# QuickTime atom dispatcher
my %QT_CONTAINER_ATOM = ('trak' => 1, 'minf' => 1, 'stbl' => 1, 'mdia' => 1);
my %QT_SIMPLE_ATOM = (
'mvhd' => \&get_mvhd,
'tkhd' => \&get_tkhd,
'tref' => \&get_tref,
'hdlr' => \&get_hdlr,
'stsz' => \&get_stsz,
'stco' => \&get_stco,
);
# pack template and member key of 'mvhd', 'tkhd', and 'hdlr' atoms.
my $QT_PMVHD = 'CCCCNNNNNnx10N9NNNNNNN';
my @QT_FMVHD = qw(
version flag1 flag2 flag3 ctime mtime tscale duration
preferred_rate preferred_volume a b c d x y u v w
preview_time preview_duration poster_time
selection_time selection_duration current_time next_track_id
);
my $QT_PTKHD = 'CCCCNNNx4Nx8nnnx2N9nx2nx2';
my @QT_FTKHD = qw(
version flag1 flag2 flag3 ctime mtime track_id duration
layer alt_group volume a b c d x y u v w width height
);
my $QT_PHDLR = 'CCCCa4a4a4';
my @QT_FHDLR = qw(
version flag1 flag2 flag3 type subtype manufacture
);
# QuickTime Cubic VR file has at least 3 tracks.
#
# track_id:1 'qtvr'
# track_id:2 'pano'
# track_id:$face ``six faces as jpeg image samplings''.
#
# track_id $face is found in 'pano' trak: $moov->{'moov'}{'trak'}{'2'}{'tref'}[0]{'ref'}[0].
#
# byte size of jpeg images at
# $moov->{'moov'}{'trak'}{$face}{'mdia'}{'minf'}{'stbl'}{'stsz'}[$i],
# offset of jpeg images at
# $moov->{'moov'}{'trak'}{$face}{'mdia'}{'minf'}{'stbl'}{'stco'}[$i],
# for face $i in 0 .. 5 respectively.
sub cube2jpeg {
my($filename) = @_;
my $basename = $filename;
$basename =~ s/[.]mov//;
my $moov = {};
open my $fd, '<', $filename or croak "main: open: $!";
binmode $fd, ':raw';
get_moov($fd, $moov, $basename);
my $stbl = fetch_stbl($moov);
extract_jpeg($fd, $stbl, $basename);
close $fd;
}
sub extract_jpeg {
my($fd, $stbl, $basename) = @_;
-e $basename or mkdir $basename;
my $blob;
for my $i (0 .. 5) {
my $offset = $stbl->{'stco'}[$i];
my $size = $stbl->{'stsz'}[$i];
seek $fd, $offset, 0;
$size == (read $fd, $blob, $size) or croak "read error";
open my $jpg, '>', "$basename/$i.jpg" or croak "open: $!";
binmode $jpg, ':raw';
print $jpg $blob;
close $jpg;
}
}
sub fetch_stbl {
my($moov) = @_;
my $trak = $moov->{'moov'}{'trak'};
exists $trak->{'1'}{'mdia'}{'hdlr'} or croak "not a qtvr";
$trak->{'1'}{'mdia'}{'hdlr'} eq 'mhlr/qtvr' or croak "not a qtvr";
exists $trak->{'1'}{'tref'}[0]{'type'} or croak "not a qtvr";
$trak->{'1'}{'tref'}[0]{'type'} eq 'pano' or croak "not a qtvr";
exists $trak->{'1'}{'tref'}[0]{'ref'}[0] or croak "not a qtvr";
$trak->{'1'}{'tref'}[0]{'ref'}[0] eq '2' or croak "not a qtvr";
exists $trak->{'2'}{'mdia'}{'hdlr'} or croak "not a qtvr";
$trak->{'2'}{'mdia'}{'hdlr'} eq 'mhlr/pano' or croak "not a pano";
exists $trak->{'2'}{'tref'}[0]{'type'} or croak "not a pano";
$trak->{'2'}{'tref'}[0]{'type'} eq 'imgt' or croak "not a pano";
exists $trak->{'2'}{'tref'}[0]{'ref'}[0] or croak "not a pano";
my $face = $trak->{'2'}{'tref'}[0]{'ref'}[0];
exists $trak->{$face}{'mdia'}{'minf'}{'stbl'}{'stco'} or croak "not a pano";
exists $trak->{$face}{'mdia'}{'minf'}{'stbl'}{'stsz'} or croak "not a pano";
my $nstco = @{$trak->{$face}{'mdia'}{'minf'}{'stbl'}{'stco'}};
my $nstsz = @{$trak->{$face}{'mdia'}{'minf'}{'stbl'}{'stsz'}};
$nstco == 6 or croak "not a cubic qtvr";
$nstsz == 6 or croak "not a cubic qtvr";
return $trak->{$face}{'mdia'}{'minf'}{'stbl'};
}
sub get_bytes {
my($fd, $len) = @_;
my $buf;
my $n = read($fd, $buf, $len) or croak "get_bytes: read: $!";
$n == $len or carp "get_bytes: too short ($n/$len)";
return $buf;
}
# get QuickTime definitions of components.
# see https://developer.apple.com/library/content/documentation/QuickTime/QTFF/QTFFPreface/qtffPreface.html
sub get_moov {
my($fd, $moov) = @_;
while (! eof($fd)) {
my($len, $type) = unpack 'Na4', get_bytes($fd, 8);
last if $len <= 0;
if ($type eq 'moov') {
get_atom($fd, $moov, $len - 8, $type);
last;
}
else {
seek $fd, $len - 8, 1;
}
}
}
sub get_atom {
my($fd, $atom, $skip, $parent) = @_;
my $node = {};
if ($parent ne 'trak') {
$node = $atom->{$parent} = {};
}
while ($skip > 0) {
my($len, $type) = unpack 'Na4', get_bytes($fd, 8);
last if $len <= 0;
$skip -= $len;
if (exists $QT_CONTAINER_ATOM{$type}) {
get_atom($fd, $node, $len - 8, $type);
}
elsif (exists $QT_SIMPLE_ATOM{$type}) {
$QT_SIMPLE_ATOM{$type}->($fd, $node, $len - 8);
}
else {
seek $fd, $len - 8, 1;
}
}
if ($parent eq 'trak') {
my $id = $node->{'tkhd'}{'track_id'};
if (! exists $atom->{'trak'}) {
$atom->{'trak'} = {};
}
$atom->{'trak'}{$id} = $node;
}
}
sub get_mvhd {
my($fd, $moov, $skip) = @_;
my %mvhd;
@mvhd{@QT_FMVHD} = unpack $QT_PMVHD, get_bytes($fd, $skip);
$moov->{'mvhd'} = \%mvhd;
}
sub get_tkhd {
my($fd, $trak, $skip) = @_;
my %tkhd;
@tkhd{@QT_FTKHD} = unpack $QT_PTKHD, get_bytes($fd, $skip);
$trak->{'tkhd'} = \%tkhd;
}
sub get_tref {
my($fd, $trak, $skip) = @_;
my @tref;
while ($skip > 0) {
my($len, $type) = unpack 'Na4', get_bytes($fd, 8);
last if $len <= 0;
$skip -= $len;
my @idents;
if ($len - 8 > 0) {
@idents = unpack "N*", get_bytes($fd, $len - 8);
}
push @tref, {'type' => $type, 'ref' => \@idents};
}
$trak->{'tref'} = \@tref;
}
sub get_hdlr {
my($fd, $mdia, $skip) = @_;
my %hdlr;
@hdlr{@QT_FHDLR} = unpack $QT_PHDLR, get_bytes($fd, $skip);
$mdia->{'hdlr'} = $hdlr{'type'} . q(/) . $hdlr{'subtype'};
}
sub get_stsz {
my($fd, $stbl, $skip, $path) = @_;
my($same_size, $count, @sizes) = unpack 'x4NNN*', get_bytes($fd, $skip);
if (@sizes) {
$stbl->{'stsz'} = \@sizes;
}
else {
$stbl->{'stsz'} = [($same_size) x $count];
}
}
sub get_stco {
my($fd, $stbl, $skip, $path) = @_;
my(@offsets) = unpack 'x8N*', get_bytes($fd, $skip);
$stbl->{'stco'} = \@offsets;
}
run();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.