anm.c 6.35 KB
Newer Older
Peter Ross's avatar
Peter Ross committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/*
 * Deluxe Paint Animation demuxer
 * Copyright (c) 2009 Peter Ross
 *
 * This file is part of FFmpeg.
 *
 * FFmpeg is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * FFmpeg 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with FFmpeg; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

/**
23
 * @file
Peter Ross's avatar
Peter Ross committed
24 25 26 27 28 29 30 31 32 33 34 35 36
 * Deluxe Paint Animation demuxer
 */

#include "libavutil/intreadwrite.h"
#include "avformat.h"

typedef struct {
    int base_record;
    unsigned int nb_records;
    int size;
} Page;

typedef struct {
37 38
    unsigned int nb_pages;    /**< total pages in file */
    unsigned int nb_records;  /**< total records in file */
Peter Ross's avatar
Peter Ross committed
39
    int page_table_offset;
40 41 42 43
#define MAX_PAGES  256        /**< Deluxe Paint hardcoded value */
    Page pt[MAX_PAGES];       /**< page table */
    int page;                 /**< current page (or AVERROR_xxx code) */
    int record;               /**< current record (with in page) */
Peter Ross's avatar
Peter Ross committed
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
} AnmDemuxContext;

#define LPF_TAG  MKTAG('L','P','F',' ')
#define ANIM_TAG MKTAG('A','N','I','M')

static int probe(AVProbeData *p)
{
    /* verify tags and video dimensions */
    if (AV_RL32(&p->buf[0])  == LPF_TAG &&
        AV_RL32(&p->buf[16]) == ANIM_TAG &&
        AV_RL16(&p->buf[20]) && AV_RL16(&p->buf[22]))
        return AVPROBE_SCORE_MAX;
    return 0;
}

/**
 * @return page containing the requested record or AVERROR_XXX
 */
static int find_record(const AnmDemuxContext *anm, int record)
{
    int i;

    if (record >= anm->nb_records)
        return AVERROR_EOF;

    for (i = 0; i < MAX_PAGES; i++) {
        const Page *p = &anm->pt[i];
        if (p->nb_records > 0 && record >= p->base_record && record < p->base_record + p->nb_records)
            return i;
    }

    return AVERROR_INVALIDDATA;
}

static int read_header(AVFormatContext *s,
                       AVFormatParameters *ap)
{
    AnmDemuxContext *anm = s->priv_data;
82
    AVIOContext *pb = s->pb;
Peter Ross's avatar
Peter Ross committed
83 84 85 86
    AVStream *st;
    int i, ret;

    url_fskip(pb, 4); /* magic number */
87
    if (avio_rl16(pb) != MAX_PAGES) {
Peter Ross's avatar
Peter Ross committed
88 89 90 91
        av_log_ask_for_sample(s, "max_pages != " AV_STRINGIFY(MAX_PAGES) "\n");
        return AVERROR_INVALIDDATA;
    }

92 93
    anm->nb_pages   = avio_rl16(pb);
    anm->nb_records = avio_rl32(pb);
Peter Ross's avatar
Peter Ross committed
94
    url_fskip(pb, 2); /* max records per page */
95 96
    anm->page_table_offset = avio_rl16(pb);
    if (avio_rl32(pb) != ANIM_TAG)
Peter Ross's avatar
Peter Ross committed
97 98 99 100 101 102
        return AVERROR_INVALIDDATA;

    /* video stream */
    st = av_new_stream(s, 0);
    if (!st)
        return AVERROR(ENOMEM);
103
    st->codec->codec_type = AVMEDIA_TYPE_VIDEO;
Peter Ross's avatar
Peter Ross committed
104 105
    st->codec->codec_id   = CODEC_ID_ANM;
    st->codec->codec_tag  = 0; /* no fourcc */
106 107 108
    st->codec->width      = avio_rl16(pb);
    st->codec->height     = avio_rl16(pb);
    if (avio_r8(pb) != 0)
Peter Ross's avatar
Peter Ross committed
109 110 111 112
        goto invalid;
    url_fskip(pb, 1); /* frame rate multiplier info */

    /* ignore last delta record (used for looping) */
113
    if (avio_r8(pb))  /* has_last_delta */
Peter Ross's avatar
Peter Ross committed
114 115 116 117
        anm->nb_records = FFMAX(anm->nb_records - 1, 0);

    url_fskip(pb, 1); /* last_delta_valid */

118
    if (avio_r8(pb) != 0)
Peter Ross's avatar
Peter Ross committed
119 120
        goto invalid;

121
    if (avio_r8(pb) != 1)
Peter Ross's avatar
Peter Ross committed
122 123 124 125
        goto invalid;

    url_fskip(pb, 1); /* other recs per frame */

126
    if (avio_r8(pb) != 1)
Peter Ross's avatar
Peter Ross committed
127 128 129
        goto invalid;

    url_fskip(pb, 32); /* record_types */
130 131
    st->nb_frames = avio_rl32(pb);
    av_set_pts_info(st, 64, 1, avio_rl16(pb));
Peter Ross's avatar
Peter Ross committed
132 133 134 135 136 137 138 139 140
    url_fskip(pb, 58);

    /* color cycling and palette data */
    st->codec->extradata_size = 16*8 + 4*256;
    st->codec->extradata      = av_mallocz(st->codec->extradata_size + FF_INPUT_BUFFER_PADDING_SIZE);
    if (!st->codec->extradata) {
        ret = AVERROR(ENOMEM);
        goto close_and_return;
    }
141
    ret = avio_read(pb, st->codec->extradata, st->codec->extradata_size);
Peter Ross's avatar
Peter Ross committed
142 143 144 145 146 147 148 149 150 151
    if (ret < 0)
        goto close_and_return;

    /* read page table */
    ret = url_fseek(pb, anm->page_table_offset, SEEK_SET);
    if (ret < 0)
        goto close_and_return;

    for (i = 0; i < MAX_PAGES; i++) {
        Page *p = &anm->pt[i];
152 153 154
        p->base_record = avio_rl16(pb);
        p->nb_records  = avio_rl16(pb);
        p->size        = avio_rl16(pb);
Peter Ross's avatar
Peter Ross committed
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
    }

    /* find page of first frame */
    anm->page = find_record(anm, 0);
    if (anm->page < 0) {
        ret = anm->page;
        goto close_and_return;
    }

    anm->record = -1;
    return 0;

invalid:
    av_log_ask_for_sample(s, NULL);
    ret = AVERROR_INVALIDDATA;

close_and_return:
    av_close_input_stream(s);
    return ret;
}

static int read_packet(AVFormatContext *s,
                       AVPacket *pkt)
{
    AnmDemuxContext *anm = s->priv_data;
180
    AVIOContext *pb = s->pb;
Peter Ross's avatar
Peter Ross committed
181 182 183 184 185 186 187
    Page *p;
    int tmp, record_size;

    if (url_feof(s->pb))
        return AVERROR(EIO);

    if (anm->page < 0)
188
        return anm->page;
Peter Ross's avatar
Peter Ross committed
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213

repeat:
    p = &anm->pt[anm->page];

    /* parse page header */
    if (anm->record < 0) {
        url_fseek(pb, anm->page_table_offset + MAX_PAGES*6 + (anm->page<<16), SEEK_SET);
        url_fskip(pb, 8 + 2*p->nb_records);
        anm->record = 0;
    }

    /* if we have fetched all records in this page, then find the
       next page and repeat */
    if (anm->record >= p->nb_records) {
        anm->page = find_record(anm, p->base_record + p->nb_records);
        if (anm->page < 0)
            return anm->page;
        anm->record = -1;
        goto repeat;
    }

    /* fetch record size */
    tmp = url_ftell(pb);
    url_fseek(pb, anm->page_table_offset + MAX_PAGES*6 + (anm->page<<16) +
              8 + anm->record * 2, SEEK_SET);
214
    record_size = avio_rl16(pb);
Peter Ross's avatar
Peter Ross committed
215 216 217 218 219 220 221
    url_fseek(pb, tmp, SEEK_SET);

    /* fetch record */
    pkt->size = av_get_packet(s->pb, pkt, record_size);
    if (pkt->size < 0)
        return pkt->size;
    if (p->base_record + anm->record == 0)
222
        pkt->flags |= AV_PKT_FLAG_KEY;
Peter Ross's avatar
Peter Ross committed
223 224 225 226 227

    anm->record++;
    return 0;
}

228
AVInputFormat ff_anm_demuxer = {
Peter Ross's avatar
Peter Ross committed
229 230 231 232 233 234 235
    "anm",
    NULL_IF_CONFIG_SMALL("Deluxe Paint Animation"),
    sizeof(AnmDemuxContext),
    probe,
    read_header,
    read_packet,
};