package Ogg::Vorbis::Header;
use 5.006;
use strict;
use warnings;
our $VERSION = '0.01';
use Inline C => 'DATA',
LIBS => '-logg -lvorbis -lvorbisfile',
INC => '-I/inc',
AUTO_INCLUDE => '#include "inc/vcedit.h"',
AUTO_INCLUDE => '#include "inc/vcedit.c"',
VERSION => '0.01',
NAME => 'Ogg::Vorbis::Header';
# constructors
# wrap this so $obj->new will work right
sub new {
my ($id, $path) = @_;
$id = ref($id) || $id;
_new($id, $path);
}
sub load {
my ($id, $path) = @_;
unless (ref($id)) {
$id = _new($id, $path);
}
$id->_load_info;
$id->_load_comments;
return $id;
}
# A number of the instance methods may be handled with perl code.
sub info {
my ($self, $key) = @_;
$self->_load_info unless $self->{INFO};
if ($key) {
return $self->{INFO}->{$key}
}
return $self->{INFO};
}
sub comment_tags {
my $self = shift;
$self->_load_comments unless $self->{COMMENTS};
return keys %{$self->{COMMENTS}};
}
sub comment {
my ($self, $key) = @_;
my $result;
return undef unless $key;
$self->_load_comments unless $self->{COMMENTS};
if (! defined ($result = $self->{COMMENTS}->{$key})) {
return undef;
}
return @{$result};
}
sub add_comments {
my ($self, @comments) = @_;
# For now play it safe limit both tag and field to minimal ascii
# will work on utf8 in field later
return undef if @comments < 2 or @comments % 2 != 0;
$self->_load_comments unless $self->{COMMENTS};
while ($#comments >= 0) {
my $key = shift @comments;
$key =~ s/[^\x20-\x3C\x3E-\x7D]//g;
$key = lc($key);
my $val = shift @comments;
$val =~ s/[^\x20-\x7D]//g;
push @{$self->{COMMENTS}->{$key}}, $val;
}
return 1;
}
sub edit_comment {
my ($self, $key, $value, $num) = @_;
$num ||= 0;
return undef unless $key and $value and $num =~ /^\d*$/;
$self->_load_comments unless $self->{COMMENTS};
my $comment = $self->{COMMENTS}->{$key};
return undef unless $comment;
$value =~ s/[^\x20-\x7D]//g;
return undef unless @$comment > $num;
my $result = $comment->[$num];
$comment->[$num] = $value;
return $result;
}
sub delete_comment {
my ($self, $key, $num) = @_;
$num ||= 0;
return undef unless $key and $num =~ /^\d*$/;
$self->_load_comments unless $self->{COMMENTS};
my $comment = $self->{COMMENTS}->{$key};
return undef unless $comment;
return undef unless @$comment > $num;
my $result = splice @$comment, $num, 1;
if (@$comment == 0) {
delete($self->{COMMENTS}->{$key});
}
return $result;
}
sub clear_comments {
my ($self, @keys) = @_;
$self->_load_comments unless $self->{COMMENTS};
if (@keys) {
foreach (@keys) {
return undef unless $self->{COMMENTS}->{$_};
delete($self->{COMMENTS}->{$_});
}
} else {
foreach (keys %{$self->{COMMENTS}}) {
delete($self->{COMMENTS}->{$_});
}
}
return 1;
}
sub path {
my $self = shift;
return $self->{PATH};
}
1;
__DATA__
=head1 NAME
POD omitted from post...
=cut
__C__
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vorbis/codec.h>
#include <vorbis/vorbisfile.h>
/* Loads info and length from the stream a fills the object's hash */
void _load_info(SV *obj)
{
OggVorbis_File vf;
vorbis_info *vi;
FILE *fd;
char *ptr;
HV *th;
HV *hash = (HV *) SvRV(obj);
/* Open the vorbis stream file */
ptr = (char *) SvIV(*(hv_fetch(hash, "_PATH", 5, 0)));
if ((fd = fopen(ptr, "r")) == NULL) {
perror("Error opening file in Ogg::Vorbis::Header::_load_info\
+n");
return;
}
if (ov_open(fd, &vf, NULL, 0) < 0) {
fclose(fd);
perror("Error opening file in Ogg::Vorbis::Header::_load_info\
+n");
return;
}
vi = ov_info(&vf, -1);
th = newHV();
hv_store(th, "version", 7, newSViv(vi->version), 0);
hv_store(th, "channels", 8, newSViv(vi->channels), 0);
hv_store(th, "rate", 4, newSViv(vi->rate), 0);
hv_store(th, "bitrate_upper", 13, newSViv(vi->bitrate_upper), 0);
hv_store(th, "bitrate_nominal", 15, newSViv(vi->bitrate_nominal),
+0);
hv_store(th, "bitrate_lower", 13, newSViv(vi->bitrate_lower), 0);
hv_store(th, "bitrate_window", 14, newSViv(vi->bitrate_window), 0)
+;
hv_store(th, "length", 6, newSVnv(ov_time_total(&vf, -1)), 0);
hv_store(hash, "INFO", 4, newRV_noinc((SV *) th), 0);
ov_clear(&vf);
}
/* Loads the commments from the stream and fills the object's hash */
void _load_comments(SV *obj)
{
OggVorbis_File vf;
vorbis_comment *vc;
FILE *fd;
HV *th;
SV *ts;
AV *ta;
char *half;
char *ptr;
int i;
HV *hash = (HV *) SvRV(obj);
/* Open the vorbis stream file */
ptr = (char *) SvIV(*(hv_fetch(hash, "_PATH", 5, 0)));
if ((fd = fopen(ptr, "r")) == NULL) {
perror("Error opening file in Ogg::Vorbis::Header::_load_comme
+nts\n");
return;
}
if (ov_open(fd, &vf, NULL, 0) < 0) {
fclose(fd);
perror("Error opening file in Ogg::Vorbis::Header::_load_comme
+nts\n");
return;
}
vc = ov_comment(&vf, -1);
th = newHV();
for (i = 0; i < vc->comments; ++i) {
half = strchr(vc->user_comments[i], '=');
if (! hv_exists(th, vc->user_comments[i],
half - vc->user_comments[i]))
+{
ta = newAV();
ts = newRV_noinc((SV*) ta);
hv_store(th, vc->user_comments[i], half - vc->user_comment
+s[i],
ts, 0);
} else {
ta = (AV*) SvRV(*(hv_fetch(th, vc->user_comments[i],
half - vc->user_comments[i], 0)));
}
av_push(ta, newSVpv(half + 1, 0));
}
hv_store(hash, "COMMENTS", 8, newRV_noinc((SV *) th), 0);
ov_clear(&vf);
}
/* Our base object constructor. Creates a blessed hash. */
SV* _new(char *class, char *path)
{
/* A few variables */
FILE *fd;
char *_path;
OggVorbis_File vf;
/* Create our new hash and the reference to it. */
HV *hash = newHV();
SV *obj_ref = newRV_noinc((SV*) hash);
/* Save an internal (c-style) rep of the path */
_path = strdup(path);
hv_store(hash, "_PATH", 5, newSViv((IV) _path), 0);
/* Open the vorbis stream file */
if ((fd = fopen(path, "r")) == NULL)
return &PL_sv_undef;
if (ov_test(fd, &vf, NULL, 0) < 0) {
fclose(fd);
return &PL_sv_undef;
}
/* Values stored at base level */
hv_store(hash, "PATH", 4, newSVpv(path, 0), 0);
/* Close our OggVorbis_File cause we don't want to keep the file
* descriptor open.
*/
ov_clear(&vf);
/* Bless the hashref to create a class object */
sv_bless(obj_ref, gv_stashpv(class, FALSE));
return obj_ref;
}
/* These comment manipulation functions use the vcedit library by
* Michael Smith. They also borrow quite a bit from vorbiscomment
* (vcomment.c) by Michael Smith and Ralph Giles.
*/
int write_vorbis (SV *obj)
{
vcedit_state *state;
vorbis_comment *vc;
char *inpath, *outpath, *mvstring, *key, *val;
FILE *fd, *fd2;
HV *hash = (HV *) SvRV(obj);
HV *chash;
AV *vals;
HE *hval;
I32 i, j, num;
/* Skip if comments hasn't been opened */
if (! hv_exists(hash, "COMMENTS", 8)) {
return 0;
}
/* Set up the input and output paths */
inpath = strdup((char *) SvIV(*(hv_fetch(hash, "_PATH", 5, 0))));
outpath = malloc(strlen(inpath) + (8 * sizeof(char)));
strcpy(outpath, inpath);
strcat(outpath, ".ovitmp");
/* Open the files */
if ((fd = fopen(inpath, "r")) == NULL) {
perror("Error opening file in Ogg::Vorbis::Header::write\n");
free(inpath);
free(outpath);
return 0;
}
if ((fd2 = fopen(outpath, "w+")) == NULL) {
fclose(fd);
free(inpath);
free(outpath);
perror("Error opening temp file in Ogg::Vorbis::Header::write\
+n");
return 0;
}
/* Setup the state and comments structs */
state = vcedit_new_state();
if (vcedit_open(state, fd) < 0) {
perror("Error opening stream in Ogg::Vorbis::Header::add_comme
+nt\n");
goto cleanup;
}
vc = vcedit_comments(state);
/* clear the old comment fields */
vorbis_comment_clear(vc);
vorbis_comment_init(vc);
/* Write the comment fields from the hash
* FIX: This doesn't preserve order, which may or may not be a pro
+blem
*/
chash = (HV *) SvRV(*(hv_fetch(hash, "COMMENTS", 8, 0)));
num = hv_iterinit(chash);
for (i = 0; i < num; ++i) {
hval = hv_iternext(chash);
key = SvPV_nolen(hv_iterkeysv(hval));
vals = (AV*) SvRV(*(hv_fetch(chash, key, strlen(key), 0)));
for (j = 0; j <= av_len(vals); ++j) {
val = SvPV_nolen(*av_fetch(vals, j, 0));
vorbis_comment_add_tag(vc, key, val);
}
}
/* Write out the new stream */
if (vcedit_write(state, fd2) < 0) {
perror("Error writing stream in Ogg::Vorbis::Header::add_comme
+nt\n");
goto cleanup;
}
fclose(fd);
fclose(fd2);
vcedit_clear(state);
mvstring = malloc(strlen(inpath) + strlen(outpath)
+ (5 * sizeof(char)));
strcpy(mvstring, "mv ");
strcat(mvstring, outpath);
strcat(mvstring, " ");
strcat(mvstring, inpath);
unlink(inpath);
system(mvstring);
unlink(outpath);
free(inpath);
free(outpath);
free(mvstring);
return 1;
cleanup:
fclose(fd);
fclose(fd2);
unlink(outpath);
free(inpath);
free(outpath);
vcedit_clear(state);
return 0;
}
/* We strdup'd the internal path string so we need to free it */
void DESTROY (SV *obj)
{
char *ptr;
HV *hash = (HV *) SvRV(obj);
ptr = (char *) SvIV(*(hv_fetch(hash, "_PATH", 5, 0)));
free(ptr);
}
|