/* * BluRay (libbluray) protocol * * Copyright (c) 2012 Petri Hintukainen <phintuka <at> users.sourceforge.net> * * 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 */ #include <libbluray/bluray.h> #include "libavutil/avstring.h" #include "libavformat/avformat.h" #include "libavformat/url.h" #include "libavutil/opt.h" #define BLURAY_PROTO_PREFIX "bluray:" #define MIN_PLAYLIST_LENGTH 180 /* 3 min */ typedef struct { const AVClass *class; BLURAY *bd; int playlist; int angle; int chapter; /*int region;*/ } BlurayContext; #define OFFSET(x) offsetof(BlurayContext, x) static const AVOption options[] = { {"playlist", "", OFFSET(playlist), AV_OPT_TYPE_INT, { .i64=-1 }, -1, 99999, AV_OPT_FLAG_DECODING_PARAM }, {"angle", "", OFFSET(angle), AV_OPT_TYPE_INT, { .i64=0 }, 0, 0xfe, AV_OPT_FLAG_DECODING_PARAM }, {"chapter", "", OFFSET(chapter), AV_OPT_TYPE_INT, { .i64=1 }, 1, 0xfffe, AV_OPT_FLAG_DECODING_PARAM }, /*{"region", "bluray player region code (1 = region A, 2 = region B, 4 = region C)", OFFSET(region), AV_OPT_TYPE_INT, { .i64=0 }, 0, 3, AV_OPT_FLAG_DECODING_PARAM },*/ {NULL} }; static const AVClass bluray_context_class = { .class_name = "bluray", .item_name = av_default_item_name, .option = options, .version = LIBAVUTIL_VERSION_INT, }; static int check_disc_info(URLContext *h) { BlurayContext *bd = h->priv_data; const BLURAY_DISC_INFO *disc_info; disc_info = bd_get_disc_info(bd->bd); if (!disc_info) { av_log(h, AV_LOG_ERROR, "bd_get_disc_info() failed\n"); return -1; } if (!disc_info->bluray_detected) { av_log(h, AV_LOG_ERROR, "BluRay disc not detected\n"); return -1; } /* AACS */ if (disc_info->aacs_detected && !disc_info->aacs_handled) { if (!disc_info->libaacs_detected) { av_log(h, AV_LOG_ERROR, "Media stream encrypted with AACS, install and configure libaacs\n"); } else { av_log(h, AV_LOG_ERROR, "Your libaacs can't decrypt this media\n"); } return -1; } /* BD+ */ if (disc_info->bdplus_detected && !disc_info->bdplus_handled) { /* if (!disc_info->libbdplus_detected) { av_log(h, AV_LOG_ERROR, "Media stream encrypted with BD+, install and configure libbdplus"); } else { */ av_log(h, AV_LOG_ERROR, "Unable to decrypt BD+ encrypted media\n"); /*}*/ return -1; } return 0; } static int bluray_close(URLContext *h) { BlurayContext *bd = h->priv_data; if (bd->bd) { bd_close(bd->bd); } return 0; } static int bluray_open(URLContext *h, const char *path, int flags) { BlurayContext *bd = h->priv_data; int num_title_idx; const char *diskname = path; av_strstart(path, BLURAY_PROTO_PREFIX, &diskname); bd->bd = bd_open(diskname, NULL); if (!bd->bd) { av_log(h, AV_LOG_ERROR, "bd_open() failed\n"); return AVERROR(EIO); } /* check if disc can be played */ if (check_disc_info(h) < 0) { return AVERROR(EIO); } /* setup player registers */ /* region code has no effect without menus if (bd->region > 0 && bd->region < 5) { av_log(h, AV_LOG_INFO, "setting region code to %d (%c)\n", bd->region, 'A' + (bd->region - 1)); bd_set_player_setting(bd->bd, BLURAY_PLAYER_SETTING_REGION_CODE, bd->region); } */ /* load title list */ num_title_idx = bd_get_titles(bd->bd, TITLES_RELEVANT, MIN_PLAYLIST_LENGTH); av_log(h, AV_LOG_INFO, "%d usable playlists:\n", num_title_idx); if (num_title_idx < 1) { return AVERROR(EIO); } /* if playlist was not given, select longest playlist */ if (bd->playlist < 0) { uint64_t duration = 0; int i; for (i = 0; i < num_title_idx; i++) { BLURAY_TITLE_INFO *info = bd_get_title_info(bd->bd, i, 0); av_log(h, AV_LOG_INFO, "playlist %05d.mpls (%d:%02d:%02d)\n", info->playlist, ((int)(info->duration / 90000) / 3600), ((int)(info->duration / 90000) % 3600) / 60, ((int)(info->duration / 90000) % 60)); if (info->duration > duration) { bd->playlist = info->playlist; duration = info->duration; } bd_free_title_info(info); } av_log(h, AV_LOG_INFO, "selected %05d.mpls\n", bd->playlist); } /* select playlist */ if (bd_select_playlist(bd->bd, bd->playlist) <= 0) { av_log(h, AV_LOG_ERROR, "bd_select_playlist(%05d.mpls) failed\n", bd->playlist); return AVERROR(EIO); } /* select angle */ if (bd->angle >= 0) { bd_select_angle(bd->bd, bd->angle); } /* select chapter */ if (bd->chapter > 1) { bd_seek_chapter(bd->bd, bd->chapter - 1); } return 0; } static int bluray_read(URLContext *h, unsigned char *buf, int size) { BlurayContext *bd = h->priv_data; int len; if (!bd || !bd->bd) { return AVERROR(EFAULT); } len = bd_read(bd->bd, buf, size); return len; } static int64_t bluray_seek(URLContext *h, int64_t pos, int whence) { BlurayContext *bd = h->priv_data; if (!bd || !bd->bd) { return AVERROR(EFAULT); } switch (whence) { case SEEK_SET: case SEEK_CUR: case SEEK_END: return bd_seek(bd->bd, pos); case AVSEEK_SIZE: return bd_get_title_size(bd->bd); } av_log(h, AV_LOG_ERROR, "Unsupported whence operation %d\n", whence); return AVERROR(EINVAL); } URLProtocol ff_bluray_protocol = { .name = "bluray", .url_close = bluray_close, .url_open = bluray_open, .url_read = bluray_read, .url_seek = bluray_seek, .priv_data_size = sizeof(BlurayContext), .priv_data_class = &bluray_context_class, };