... | ... |
@@ -1870,6 +1870,7 @@ spdif_muxer_select="aac_parser" |
1870 | 1870 |
tak_demuxer_select="tak_parser" |
1871 | 1871 |
tg2_muxer_select="mov_muxer" |
1872 | 1872 |
tgp_muxer_select="mov_muxer" |
1873 |
+vobsub_demuxer_select="mpegps_demuxer" |
|
1873 | 1874 |
w64_demuxer_deps="wav_demuxer" |
1874 | 1875 |
|
1875 | 1876 |
# indevs / outdevs |
... | ... |
@@ -924,6 +924,7 @@ performance on systems without hardware floating point support). |
924 | 924 |
@item SAMI @tab @tab X @tab @tab X |
925 | 925 |
@item SubRip (SRT) @tab X @tab X @tab X @tab X |
926 | 926 |
@item SubViewer @tab @tab X @tab @tab X |
927 |
+@item VobSub (IDX+SUB) @tab @tab X @tab @tab X |
|
927 | 928 |
@item 3GPP Timed Text @tab @tab @tab X @tab X |
928 | 929 |
@item WebVTT @tab @tab X @tab @tab X |
929 | 930 |
@item XSUB @tab @tab @tab X @tab X |
... | ... |
@@ -258,6 +258,7 @@ void av_register_all(void) |
258 | 258 |
REGISTER_MUXDEMUX (VC1T, vc1t); |
259 | 259 |
REGISTER_DEMUXER (VIVO, vivo); |
260 | 260 |
REGISTER_DEMUXER (VMD, vmd); |
261 |
+ REGISTER_DEMUXER (VOBSUB, vobsub); |
|
261 | 262 |
REGISTER_MUXDEMUX (VOC, voc); |
262 | 263 |
REGISTER_DEMUXER (VQF, vqf); |
263 | 264 |
REGISTER_DEMUXER (W64, w64); |
... | ... |
@@ -23,6 +23,11 @@ |
23 | 23 |
#include "internal.h" |
24 | 24 |
#include "mpeg.h" |
25 | 25 |
|
26 |
+#if CONFIG_VOBSUB_DEMUXER |
|
27 |
+# include "subtitles.h" |
|
28 |
+# include "libavutil/bprint.h" |
|
29 |
+#endif |
|
30 |
+ |
|
26 | 31 |
#undef NDEBUG |
27 | 32 |
#include <assert.h> |
28 | 33 |
|
... | ... |
@@ -103,6 +108,10 @@ typedef struct MpegDemuxContext { |
103 | 103 |
int32_t header_state; |
104 | 104 |
unsigned char psm_es_type[256]; |
105 | 105 |
int sofdec; |
106 |
+#if CONFIG_VOBSUB_DEMUXER |
|
107 |
+ AVFormatContext *sub_ctx; |
|
108 |
+ FFDemuxSubtitlesQueue q; |
|
109 |
+#endif |
|
106 | 110 |
} MpegDemuxContext; |
107 | 111 |
|
108 | 112 |
static int mpegps_read_header(AVFormatContext *s) |
... | ... |
@@ -574,3 +583,257 @@ AVInputFormat ff_mpegps_demuxer = { |
574 | 574 |
.read_timestamp = mpegps_read_dts, |
575 | 575 |
.flags = AVFMT_SHOW_IDS | AVFMT_TS_DISCONT, |
576 | 576 |
}; |
577 |
+ |
|
578 |
+#if CONFIG_VOBSUB_DEMUXER |
|
579 |
+ |
|
580 |
+#define REF_STRING "# VobSub index file," |
|
581 |
+ |
|
582 |
+static int vobsub_probe(AVProbeData *p) |
|
583 |
+{ |
|
584 |
+ if (!strncmp(p->buf, REF_STRING, sizeof(REF_STRING) - 1)) |
|
585 |
+ return AVPROBE_SCORE_MAX; |
|
586 |
+ return 0; |
|
587 |
+} |
|
588 |
+ |
|
589 |
+static int vobsub_read_header(AVFormatContext *s) |
|
590 |
+{ |
|
591 |
+ int i, header_size, ret = 0, header_parsed = 0, langidx = 0; |
|
592 |
+ MpegDemuxContext *vobsub = s->priv_data; |
|
593 |
+ char *sub_name = NULL; |
|
594 |
+ size_t fname_len; |
|
595 |
+ char *ext, *header_str; |
|
596 |
+ AVBPrint header; |
|
597 |
+ int64_t delay = 0; |
|
598 |
+ AVStream *st = NULL; |
|
599 |
+ |
|
600 |
+ sub_name = av_strdup(s->filename); |
|
601 |
+ fname_len = strlen(sub_name); |
|
602 |
+ ext = sub_name - 3 + fname_len; |
|
603 |
+ if (fname_len < 4 || *(ext - 1) != '.') { |
|
604 |
+ av_log(s, AV_LOG_ERROR, "The input index filename is too short " |
|
605 |
+ "to guess the associated .SUB file\n"); |
|
606 |
+ ret = AVERROR_INVALIDDATA; |
|
607 |
+ goto end; |
|
608 |
+ } |
|
609 |
+ memcpy(ext, !strncmp(ext, "IDX", 3) ? "SUB" : "sub", 3); |
|
610 |
+ av_log(s, AV_LOG_VERBOSE, "IDX/SUB: %s -> %s\n", s->filename, sub_name); |
|
611 |
+ ret = avformat_open_input(&vobsub->sub_ctx, sub_name, &ff_mpegps_demuxer, NULL); |
|
612 |
+ if (ret < 0) { |
|
613 |
+ av_log(s, AV_LOG_ERROR, "Unable to open %s as MPEG subtitles\n", sub_name); |
|
614 |
+ goto end; |
|
615 |
+ } |
|
616 |
+ |
|
617 |
+ av_bprint_init(&header, 0, AV_BPRINT_SIZE_UNLIMITED); |
|
618 |
+ while (!url_feof(s->pb)) { |
|
619 |
+ char line[2048]; |
|
620 |
+ int len = ff_get_line(s->pb, line, sizeof(line)); |
|
621 |
+ |
|
622 |
+ if (!len) |
|
623 |
+ break; |
|
624 |
+ |
|
625 |
+ line[strcspn(line, "\r\n")] = 0; |
|
626 |
+ |
|
627 |
+ if (!strncmp(line, "id:", 3)) { |
|
628 |
+ int n, stream_id = 0; |
|
629 |
+ char id[64] = {0}; |
|
630 |
+ |
|
631 |
+ n = sscanf(line, "id: %63[^,], index: %u", id, &stream_id); |
|
632 |
+ if (n != 2) { |
|
633 |
+ av_log(s, AV_LOG_WARNING, "Unable to parse index line '%s', " |
|
634 |
+ "assuming 'id: und, index: 0'\n", line); |
|
635 |
+ strcpy(id, "und"); |
|
636 |
+ stream_id = 0; |
|
637 |
+ } |
|
638 |
+ |
|
639 |
+ st = avformat_new_stream(s, NULL); |
|
640 |
+ if (!st) { |
|
641 |
+ ret = AVERROR(ENOMEM); |
|
642 |
+ goto end; |
|
643 |
+ } |
|
644 |
+ st->id = stream_id; |
|
645 |
+ st->codec->codec_type = AVMEDIA_TYPE_SUBTITLE; |
|
646 |
+ st->codec->codec_id = AV_CODEC_ID_DVD_SUBTITLE; |
|
647 |
+ av_dict_set(&st->metadata, "language", id, 0); |
|
648 |
+ av_log(s, AV_LOG_DEBUG, "IDX stream[%d] id=%s\n", stream_id, id); |
|
649 |
+ header_parsed = 1; |
|
650 |
+ |
|
651 |
+ } else if (st && !strncmp(line, "timestamp:", 10)) { |
|
652 |
+ AVPacket *sub; |
|
653 |
+ int hh, mm, ss, ms; |
|
654 |
+ int64_t pos, timestamp; |
|
655 |
+ const char *p = line + 10; |
|
656 |
+ |
|
657 |
+ if (sscanf(p, "%02d:%02d:%02d:%03d, filepos: %"PRIx64, |
|
658 |
+ &hh, &mm, &ss, &ms, &pos) != 5) { |
|
659 |
+ av_log(s, AV_LOG_ERROR, "Unable to parse timestamp line '%s', " |
|
660 |
+ "abort parsing\n", line); |
|
661 |
+ break; |
|
662 |
+ } |
|
663 |
+ timestamp = (hh*3600LL + mm*60LL + ss) * 1000LL + ms + delay; |
|
664 |
+ timestamp = av_rescale_q(timestamp, (AVRational){1,1000}, st->time_base); |
|
665 |
+ |
|
666 |
+ sub = ff_subtitles_queue_insert(&vobsub->q, "", 0, 0); |
|
667 |
+ if (!sub) { |
|
668 |
+ ret = AVERROR(ENOMEM); |
|
669 |
+ goto end; |
|
670 |
+ } |
|
671 |
+ sub->pos = pos; |
|
672 |
+ sub->pts = timestamp; |
|
673 |
+ sub->stream_index = s->nb_streams - 1; |
|
674 |
+ |
|
675 |
+ } else if (st && !strncmp(line, "alt:", 4)) { |
|
676 |
+ const char *p = line + 4; |
|
677 |
+ |
|
678 |
+ while (*p == ' ') |
|
679 |
+ p++; |
|
680 |
+ av_dict_set(&st->metadata, "title", p, 0); |
|
681 |
+ av_log(s, AV_LOG_DEBUG, "IDX stream[%d] name=%s\n", st->id, p); |
|
682 |
+ header_parsed = 1; |
|
683 |
+ |
|
684 |
+ } else if (!strncmp(line, "delay:", 6)) { |
|
685 |
+ int sign = 1, hh = 0, mm = 0, ss = 0, ms = 0; |
|
686 |
+ const char *p = line + 6; |
|
687 |
+ |
|
688 |
+ while (*p == ' ') |
|
689 |
+ p++; |
|
690 |
+ if (*p == '-' || *p == '+') { |
|
691 |
+ sign = *p == '-' ? -1 : 1; |
|
692 |
+ p++; |
|
693 |
+ } |
|
694 |
+ sscanf(p, "%d:%d:%d:%d", &hh, &mm, &ss, &ms); |
|
695 |
+ delay = ((hh*3600LL + mm*60LL + ss) * 1000LL + ms) * sign; |
|
696 |
+ |
|
697 |
+ } else if (!strncmp(line, "langidx:", 8)) { |
|
698 |
+ const char *p = line + 8; |
|
699 |
+ |
|
700 |
+ if (sscanf(p, "%d", &langidx) != 1) |
|
701 |
+ av_log(s, AV_LOG_ERROR, "Invalid langidx specified\n"); |
|
702 |
+ |
|
703 |
+ } else if (!header_parsed) { |
|
704 |
+ if (line[0] && line[0] != '#') |
|
705 |
+ av_bprintf(&header, "%s\n", line); |
|
706 |
+ } |
|
707 |
+ } |
|
708 |
+ |
|
709 |
+ if (langidx < s->nb_streams) |
|
710 |
+ s->streams[langidx]->disposition |= AV_DISPOSITION_DEFAULT; |
|
711 |
+ |
|
712 |
+ ff_subtitles_queue_finalize(&vobsub->q); |
|
713 |
+ |
|
714 |
+ if (!av_bprint_is_complete(&header)) { |
|
715 |
+ av_bprint_finalize(&header, NULL); |
|
716 |
+ ret = AVERROR(ENOMEM); |
|
717 |
+ goto end; |
|
718 |
+ } |
|
719 |
+ av_bprint_finalize(&header, &header_str); |
|
720 |
+ header_size = header.len + 1; |
|
721 |
+ for (i = 0; i < s->nb_streams; i++) { |
|
722 |
+ AVStream *sub_st = s->streams[i]; |
|
723 |
+ sub_st->codec->extradata = av_strdup(header_str); |
|
724 |
+ sub_st->codec->extradata_size = header_size; |
|
725 |
+ } |
|
726 |
+ av_free(header_str); |
|
727 |
+ |
|
728 |
+end: |
|
729 |
+ av_free(sub_name); |
|
730 |
+ return ret; |
|
731 |
+} |
|
732 |
+ |
|
733 |
+static int vobsub_read_packet(AVFormatContext *s, AVPacket *pkt) |
|
734 |
+{ |
|
735 |
+ MpegDemuxContext *vobsub = s->priv_data; |
|
736 |
+ FFDemuxSubtitlesQueue *q = &vobsub->q; |
|
737 |
+ AVIOContext *pb = vobsub->sub_ctx->pb; |
|
738 |
+ int ret, psize, len16 = -1; |
|
739 |
+ AVPacket idx_pkt; |
|
740 |
+ |
|
741 |
+ ret = ff_subtitles_queue_read_packet(q, &idx_pkt); |
|
742 |
+ if (ret < 0) |
|
743 |
+ return ret; |
|
744 |
+ |
|
745 |
+ /* compute maximum packet size using the next packet position. This is |
|
746 |
+ * useful when the len in the header is non-sense */ |
|
747 |
+ if (q->current_sub_idx < q->nb_subs) { |
|
748 |
+ psize = q->subs[q->current_sub_idx].pos - idx_pkt.pos; |
|
749 |
+ } else { |
|
750 |
+ int64_t fsize = avio_size(pb); |
|
751 |
+ psize = fsize < 0 ? 0xffff : fsize - idx_pkt.pos; |
|
752 |
+ } |
|
753 |
+ |
|
754 |
+ avio_seek(pb, idx_pkt.pos, SEEK_SET); |
|
755 |
+ |
|
756 |
+ av_init_packet(pkt); |
|
757 |
+ pkt->size = 0; |
|
758 |
+ pkt->data = NULL; |
|
759 |
+ |
|
760 |
+ do { |
|
761 |
+ int n, to_read, startcode; |
|
762 |
+ int64_t pts, dts; |
|
763 |
+ |
|
764 |
+ ret = mpegps_read_pes_header(vobsub->sub_ctx, NULL, &startcode, &pts, &dts); |
|
765 |
+ if (ret < 0) |
|
766 |
+ return ret; |
|
767 |
+ to_read = ret & 0xffff; |
|
768 |
+ |
|
769 |
+ /* this prevents reads above the current packet */ |
|
770 |
+ if (pkt->size + to_read > psize) |
|
771 |
+ break; |
|
772 |
+ |
|
773 |
+ /* if the len is computed, we check for overread */ |
|
774 |
+ if (len16 != -1 && pkt->size + to_read > len16) |
|
775 |
+ break; |
|
776 |
+ |
|
777 |
+ /* the current chunk doesn't match the stream index (unlikely) */ |
|
778 |
+ if ((startcode & 0x1f) != idx_pkt.stream_index) |
|
779 |
+ break; |
|
780 |
+ |
|
781 |
+ ret = av_grow_packet(pkt, to_read); |
|
782 |
+ if (ret < 0) |
|
783 |
+ return ret; |
|
784 |
+ |
|
785 |
+ n = avio_read(pb, pkt->data + (pkt->size - to_read), to_read); |
|
786 |
+ if (n < to_read) |
|
787 |
+ pkt->size -= to_read - n; |
|
788 |
+ |
|
789 |
+ /* first chunk contains the total len of the packet to raise */ |
|
790 |
+ if (len16 == -1 && n > 2) |
|
791 |
+ len16 = AV_RB16(pkt->data); |
|
792 |
+ } while (len16 != -1 && pkt->size != len16); |
|
793 |
+ |
|
794 |
+ pkt->pts = pkt->dts = idx_pkt.pts; |
|
795 |
+ pkt->pos = idx_pkt.pos; |
|
796 |
+ pkt->stream_index = idx_pkt.stream_index; |
|
797 |
+ |
|
798 |
+ return 0; |
|
799 |
+} |
|
800 |
+ |
|
801 |
+static int vobsub_read_seek(AVFormatContext *s, int stream_index, |
|
802 |
+ int64_t min_ts, int64_t ts, int64_t max_ts, int flags) |
|
803 |
+{ |
|
804 |
+ MpegDemuxContext *vobsub = s->priv_data; |
|
805 |
+ return ff_subtitles_queue_seek(&vobsub->q, s, stream_index, |
|
806 |
+ min_ts, ts, max_ts, flags); |
|
807 |
+} |
|
808 |
+ |
|
809 |
+static int vobsub_read_close(AVFormatContext *s) |
|
810 |
+{ |
|
811 |
+ MpegDemuxContext *vobsub = s->priv_data; |
|
812 |
+ ff_subtitles_queue_clean(&vobsub->q); |
|
813 |
+ if (vobsub->sub_ctx) |
|
814 |
+ avformat_close_input(&vobsub->sub_ctx); |
|
815 |
+ return 0; |
|
816 |
+} |
|
817 |
+ |
|
818 |
+AVInputFormat ff_vobsub_demuxer = { |
|
819 |
+ .name = "vobsub", |
|
820 |
+ .long_name = NULL_IF_CONFIG_SMALL("VobSub subtitle format"), |
|
821 |
+ .priv_data_size = sizeof(MpegDemuxContext), |
|
822 |
+ .read_probe = vobsub_probe, |
|
823 |
+ .read_header = vobsub_read_header, |
|
824 |
+ .read_packet = vobsub_read_packet, |
|
825 |
+ .read_seek2 = vobsub_read_seek, |
|
826 |
+ .read_close = vobsub_read_close, |
|
827 |
+ .flags = AVFMT_SHOW_IDS, |
|
828 |
+ .extensions = "idx", |
|
829 |
+}; |
|
830 |
+#endif |
... | ... |
@@ -30,7 +30,7 @@ |
30 | 30 |
#include "libavutil/avutil.h" |
31 | 31 |
|
32 | 32 |
#define LIBAVFORMAT_VERSION_MAJOR 54 |
33 |
-#define LIBAVFORMAT_VERSION_MINOR 47 |
|
33 |
+#define LIBAVFORMAT_VERSION_MINOR 48 |
|
34 | 34 |
#define LIBAVFORMAT_VERSION_MICRO 100 |
35 | 35 |
|
36 | 36 |
#define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \ |