MpvAudioOutput::MpvAudioOutput() : state_(AudioState::Stopped), seek_offset_(-1), volumeNeverSet_(true) { setlocale(LC_NUMERIC, "C"); handle_ = mpv::qt::Handle::FromRawHandle(mpv_create()); if (static_cast<mpv_handle *>(handle_) == nullptr) qDebug() << "Cannot mpv_create()"; set_option("video", "no"); set_option("softvol", "yes"); set_option("ytdl", "yes"); set_property("audio-client-name", "fubar"); int r = mpv_initialize(handle_); if (r < 0) { qDebug() << "Failed to initialize mpv backend: " << mpv_error_string(r); // raise Exception(""); } thread_.reset(new std::thread([this] { event_loop(); })); observe_property("playback-time"); observe_property("idle", MPV_FORMAT_FLAG); observe_property("pause", MPV_FORMAT_FLAG); observe_property("duration", MPV_FORMAT_DOUBLE); observe_property("metadata", MPV_FORMAT_NODE); r = mpv_request_log_messages(handle_, "warn"); if (r < 0) qDebug() << "mpv_request_log_messages failed: " << mpv_error_string(r); }
void MpvHandler::Debug(QString level) { if(mpv) { QByteArray tmp = level.toUtf8(); mpv_request_log_messages(mpv, tmp.constData()); } }
int main(int argc, char *argv[]) { if (argc != 2) { printf("pass a single media file as argument\n"); return 1; } mpv_handle *ctx = mpv_create(); if (!ctx) { printf("failed creating context\n"); return 1; } // Enable default key bindings, so the user can actually interact with // the player (and e.g. close the window). check_error(mpv_set_option_string(ctx, "input-default-bindings", "yes")); mpv_set_option_string(ctx, "input-vo-keyboard", "yes"); int val = 1; check_error(mpv_set_option(ctx, "osc", MPV_FORMAT_FLAG, &val)); // Done setting up options. check_error(mpv_initialize(ctx)); check_error(mpv_request_log_messages(ctx, "v")); check_error(mpv_stream_cb_add_ro(ctx, "myprotocol", argv[1], open_fn)); // Play this file. const char *cmd[] = {"loadfile", "myprotocol://fake", NULL}; check_error(mpv_command(ctx, cmd)); // Let it play, and wait until the user quits. while (1) { mpv_event *event = mpv_wait_event(ctx, 10000); if (event->event_id == MPV_EVENT_LOG_MESSAGE) { struct mpv_event_log_message *msg = (struct mpv_event_log_message *)event->data; printf("[%s] %s: %s", msg->prefix, msg->level, msg->text); continue; } printf("event: %s\n", mpv_event_name(event->event_id)); if (event->event_id == MPV_EVENT_SHUTDOWN) break; } mpv_terminate_destroy(ctx); return 0; }
bool PlayerComponent::componentInitialize() { m_mpv = mpv::qt::Handle::FromRawHandle(mpv_create()); if (!m_mpv) throw FatalException(tr("Failed to load mpv.")); mpv_request_log_messages(m_mpv, "terminal-default"); mpv_set_option_string(m_mpv, "msg-level", "all=v"); // No mouse events mpv_set_option_string(m_mpv, "input-cursor", "no"); mpv_set_option_string(m_mpv, "cursor-autohide", "no"); mpv_set_option_string(m_mpv, "config", "yes"); mpv_set_option_string(m_mpv, "config-dir", Paths::dataDir().toUtf8().data()); // We don't need this, so avoid initializing fontconfig. mpv_set_option_string(m_mpv, "use-text-osd", "no"); // This forces the player not to rebase playback time to 0 with mkv. We // require this, because mkv transcoding lets files start at times other // than 0, and web-client expects that we return these times unchanged. mpv::qt::set_option_variant(m_mpv, "demuxer-mkv-probe-start-time", false); // Always use the system mixer. mpv_set_option_string(m_mpv, "softvol", "no"); // Just discard audio output if no audio device could be opened. This gives // us better flexibility how to react to such errors (instead of just // aborting playback immediately). mpv_set_option_string(m_mpv, "audio-fallback-to-null", "yes"); // Do not let the decoder downmix (better customization for us). mpv::qt::set_option_variant(m_mpv, "ad-lavc-downmix", false); // Make it load the hwdec interop, so hwdec can be enabled at runtime. mpv::qt::set_option_variant(m_mpv, "hwdec-preload", "auto"); // User-visible application name used by some audio APIs (at least PulseAudio). mpv_set_option_string(m_mpv, "audio-client-name", QCoreApplication::applicationName().toUtf8().data()); // User-visible stream title used by some audio APIs (at least PulseAudio and wasapi). mpv_set_option_string(m_mpv, "title", QCoreApplication::applicationName().toUtf8().data()); // Apply some low-memory settings on RPI, which is relatively memory-constrained. #ifdef TARGET_RPI // The backbuffer makes seeking back faster (without having to do a HTTP-level seek) mpv::qt::set_option_variant(m_mpv, "cache-backbuffer", 10 * 1024); // KB // The demuxer queue is used for the readahead, and also for dealing with badly // interlaved audio/video. Setting it too low increases sensitivity to network // issues, and could cause playback failure with "bad" files. mpv::qt::set_option_variant(m_mpv, "demuxer-max-bytes", 50 * 1024 * 1024); // bytes #endif mpv_observe_property(m_mpv, 0, "pause", MPV_FORMAT_FLAG); mpv_observe_property(m_mpv, 0, "cache-buffering-state", MPV_FORMAT_INT64); mpv_observe_property(m_mpv, 0, "playback-time", MPV_FORMAT_DOUBLE); mpv_observe_property(m_mpv, 0, "vo-configured", MPV_FORMAT_FLAG); mpv_observe_property(m_mpv, 0, "duration", MPV_FORMAT_DOUBLE); mpv_observe_property(m_mpv, 0, "audio-device-list", MPV_FORMAT_NODE); connect(this, &PlayerComponent::onMpvEvents, this, &PlayerComponent::handleMpvEvents, Qt::QueuedConnection); mpv_set_wakeup_callback(m_mpv, wakeup_cb, this); if (mpv_initialize(m_mpv) < 0) throw FatalException(tr("Failed to initialize mpv.")); // Setup a hook with the ID 1, which is run during the file is loaded. // Used to delay playback start for display framerate switching. // (See handler in handleMpvEvent() for details.) mpv::qt::command_variant(m_mpv, QStringList() << "hook-add" << "on_load" << "1" << "0"); updateAudioDeviceList(); setAudioConfiguration(); updateSubtitleSettings(); updateVideoSettings(); connect(SettingsComponent::Get().getSection(SETTINGS_SECTION_VIDEO), &SettingsSection::valuesUpdated, this, &PlayerComponent::updateVideoSettings); connect(SettingsComponent::Get().getSection(SETTINGS_SECTION_SUBTITLES), &SettingsSection::valuesUpdated, this, &PlayerComponent::updateSubtitleSettings); connect(SettingsComponent::Get().getSection(SETTINGS_SECTION_AUDIO), &SettingsSection::valuesUpdated, this, &PlayerComponent::setAudioConfiguration); return true; }
void MpvHandler::MsgLevel(QString level) { QByteArray tmp = level.toUtf8(); mpv_request_log_messages(mpv, tmp.constData()); setMsgLevel(level); }
int main(int argc, char **argv) { /** * Parse options */ bool verbose = false; bool debug = false; bool opt_track_number = false; bool opt_chapter_number = false; bool opt_filename = false; bool valid_preset = false; uint16_t arg_track_number = 0; int long_index = 0; int opt = 0; opterr = 1; uint8_t arg_first_chapter = 1; uint8_t arg_last_chapter = 99; char *token = NULL; char *token_filename = NULL; char tmp_filename[5] = {'\0'}; char dvd_mpv_args[13] = {'\0'}; char dvd_mpv_first_chapter[5] = {'\0'}; char dvd_mpv_last_chapter[5] = {'\0'}; mpv_handle *dvd_mpv = NULL; mpv_event *dvd_mpv_event = NULL; struct mpv_event_log_message *dvd_mpv_log_message = NULL; // Video Title Set struct dvd_vts dvd_vts[99]; const char str_options[] = "Ac:dehl:o:p:t:Vvz"; struct option long_options[] = { { "chapters", required_argument, 0, 'c' }, { "track", required_argument, 0, 't' }, { "alang", required_argument, 0, 'l' }, { "aid", required_argument, 0, 'A' }, { "deinterlace", no_argument, 0, 'd' }, { "detelecine", no_argument, 0, 'e' }, { "preset", required_argument, 0, 'p' }, { "output", required_argument, 0, 'o' }, { "help", no_argument, 0, 'h' }, { "version", no_argument, 0, 'V' }, { "verbose", no_argument, 0, 'v' }, { "debug", no_argument, 0, 'z' }, { 0, 0, 0, 0 } }; struct dvd_trip dvd_trip; dvd_trip.track = 1; dvd_trip.first_chapter = 1; dvd_trip.last_chapter = 99; memset(dvd_mpv_first_chapter, '\0', sizeof(dvd_mpv_first_chapter)); memset(dvd_mpv_last_chapter, '\0', sizeof(dvd_mpv_last_chapter)); memset(dvd_trip.filename, '\0', sizeof(dvd_trip.filename)); memset(dvd_trip.container, '\0', sizeof(dvd_trip.container)); strcpy(dvd_trip.container, "mkv"); memset(dvd_trip.preset, '\0', sizeof(dvd_trip.preset)); strcpy(dvd_trip.preset, "medium"); memset(dvd_trip.vcodec, '\0', sizeof(dvd_trip.vcodec)); memset(dvd_trip.vcodec_preset, '\0', sizeof(dvd_trip.vcodec_preset)); memset(dvd_trip.vcodec_opts, '\0', sizeof(dvd_trip.vcodec_opts)); memset(dvd_trip.vcodec_log_level, '\0', sizeof(dvd_trip.vcodec_log_level)); memset(dvd_trip.color_opts, '\0', sizeof(dvd_trip.color_opts)); memset(dvd_trip.acodec, '\0', sizeof(dvd_trip.acodec)); memset(dvd_trip.acodec_opts, '\0', sizeof(dvd_trip.acodec_opts)); memset(dvd_trip.audio_lang, '\0', sizeof(dvd_trip.audio_lang)); memset(dvd_trip.audio_aid, '\0', sizeof(dvd_trip.audio_aid)); memset(dvd_trip.vf_opts, '\0', sizeof(dvd_trip.vf_opts)); dvd_trip.crf = 28; memset(dvd_trip.fps, '\0', sizeof(dvd_trip.fps)); dvd_trip.deinterlace = false; dvd_trip.detelecine = false; dvd_trip.pass = 1; while((opt = getopt_long(argc, argv, str_options, long_options, &long_index )) != -1) { switch(opt) { case 'A': strncpy(dvd_trip.audio_aid, optarg, 3); break; case 'c': opt_chapter_number = true; token = strtok(optarg, "-"); { if(strlen(token) > 2) { fprintf(stderr, "Chapter range must be between 1 and 99\n"); return 1; } arg_first_chapter = (uint8_t)strtoumax(token, NULL, 0); } token = strtok(NULL, "-"); if(token != NULL) { if(strlen(token) > 2) { fprintf(stderr, "Chapter range must be between 1 and 99\n"); return 1; } arg_last_chapter = (uint8_t)strtoumax(token, NULL, 0); } if(arg_first_chapter == 0) arg_first_chapter = 1; if(arg_last_chapter < arg_first_chapter) arg_last_chapter = arg_first_chapter; break; case 'd': dvd_trip.deinterlace = true; break; case 'e': dvd_trip.detelecine = true; break; case 'h': print_usage(DVD_INFO_PROGRAM); return 0; case 'l': strncpy(dvd_trip.audio_lang, optarg, 2); break; case 'p': if(strncmp(optarg, "low", 3) == 0) { strcpy(dvd_trip.preset, "low"); } else if(strncmp(optarg, "medium", 6) == 0) { strcpy(dvd_trip.preset, "medium"); } else if(strncmp(optarg, "high", 4) == 0) { strcpy(dvd_trip.preset, "high"); } else if(strncmp(optarg, "insane", 6) == 0) { strcpy(dvd_trip.preset, "insane"); } else { printf("dvd_trip [error]: valid presets - low medium high insane\n"); return 1; } break; case 'o': opt_filename = true; strncpy(dvd_trip.filename, optarg, PATH_MAX - 1); token_filename = strtok(optarg, "."); // Choose preset from file extension while(token_filename != NULL) { snprintf(tmp_filename, 5, "%s", token_filename); token_filename = strtok(NULL, "."); if(token_filename == NULL && strlen(tmp_filename) == 3 && strncmp(tmp_filename, "mkv", 3) == 0) { strncpy(dvd_trip.container, "mkv", 4); valid_preset = true; } else if(token_filename == NULL && strlen(tmp_filename) == 3 && strncmp(tmp_filename, "mp4", 3) == 0) { strncpy(dvd_trip.container, "mp4", 4); valid_preset = true; } else if(token_filename == NULL && strlen(tmp_filename) == 4 && strncmp(tmp_filename, "webm", 4) == 0) { strncpy(dvd_trip.container, "webm", 5); valid_preset = true; } } if(!valid_preset) { printf("dvd_trip [error]: output filename extension must be one of: .mkv, .mp4, .webm\n"); return 1; } break; case 't': opt_track_number = true; arg_track_number = (uint16_t)strtoumax(optarg, NULL, 0); break; case 'V': print_version("dvd_trip"); return 0; case 'v': verbose = true; break; case 'z': verbose = true; debug = true; break; // ignore unknown arguments case '?': print_usage("dvd_trip"); return 1; // let getopt_long set the variable case 0: default: break; } } const char *device_filename = DEFAULT_DVD_DEVICE; if (argv[optind]) device_filename = argv[optind]; if(access(device_filename, F_OK) != 0) { fprintf(stderr, "cannot access %s\n", device_filename); return 1; } // Check to see if device can be opened int dvd_fd = 0; dvd_fd = dvd_device_open(device_filename); if(dvd_fd < 0) { fprintf(stderr, "dvd_trip: error opening %s\n", device_filename); return 1; } dvd_device_close(dvd_fd); #ifdef __linux__ // Poll drive status if it is hardware if(dvd_device_is_hardware(device_filename)) { // Wait for the drive to become ready if(!dvd_drive_has_media(device_filename)) { fprintf(stderr, "drive status: "); dvd_drive_display_status(device_filename); return 1; } } #endif dvd_reader_t *dvdread_dvd = NULL; dvdread_dvd = DVDOpen(device_filename); if(!dvdread_dvd) { fprintf(stderr, "* dvdread could not open %s\n", device_filename); return 1; } ifo_handle_t *vmg_ifo = NULL; vmg_ifo = ifoOpen(dvdread_dvd, 0); if(vmg_ifo == NULL) { fprintf(stderr, "* Could not open IFO zero\n"); DVDClose(dvdread_dvd); return 1; } // DVD struct dvd_info dvd_info; memset(dvd_info.dvdread_id, '\0', sizeof(dvd_info.dvdread_id)); dvd_info.video_title_sets = dvd_video_title_sets(vmg_ifo); dvd_info.side = 1; memset(dvd_info.title, '\0', sizeof(dvd_info.title)); memset(dvd_info.provider_id, '\0', sizeof(dvd_info.provider_id)); memset(dvd_info.vmg_id, '\0', sizeof(dvd_info.vmg_id)); dvd_info.tracks = dvd_tracks(vmg_ifo); dvd_info.longest_track = 1; dvd_title(dvd_info.title, device_filename); printf("Disc title: %s\n", dvd_info.title); uint16_t num_ifos = 1; num_ifos = vmg_ifo->vts_atrt->nr_of_vtss; if(num_ifos < 1) { fprintf(stderr, "* DVD has no title IFOs?!\n"); fprintf(stderr, "* Most likely a bug in libdvdread or a bad master or problems reading the disc\n"); ifoClose(vmg_ifo); DVDClose(dvdread_dvd); return 1; } // Track struct dvd_track dvd_track; memset(&dvd_track, 0, sizeof(dvd_track)); struct dvd_track dvd_tracks[DVD_MAX_TRACKS]; memset(&dvd_tracks, 0, sizeof(dvd_track) * dvd_info.tracks); // Cells struct dvd_cell dvd_cell; dvd_cell.cell = 1; memset(dvd_cell.length, '\0', sizeof(dvd_cell.length)); snprintf(dvd_cell.length, DVD_CELL_LENGTH + 1, "00:00:00.000"); dvd_cell.msecs = 0; // Open first IFO uint16_t vts = 1; ifo_handle_t *vts_ifo = NULL; vts_ifo = ifoOpen(dvdread_dvd, vts); if(vts_ifo == NULL) { fprintf(stderr, "* Could not open VTS_IFO for track %u\n", 1); return 1; } ifoClose(vts_ifo); vts_ifo = NULL; // Create an array of all the IFOs ifo_handle_t *vts_ifos[DVD_MAX_VTS_IFOS]; vts_ifos[0] = NULL; for(vts = 1; vts < dvd_info.video_title_sets + 1; vts++) { dvd_vts[vts].vts = vts; dvd_vts[vts].valid = false; dvd_vts[vts].blocks = 0; dvd_vts[vts].filesize = 0; dvd_vts[vts].vobs = 0; dvd_vts[vts].tracks = 0; dvd_vts[vts].valid_tracks = 0; dvd_vts[vts].invalid_tracks = 0; vts_ifos[vts] = ifoOpen(dvdread_dvd, vts); if(vts_ifos[vts] == NULL) { dvd_vts[vts].valid = false; vts_ifos[vts] = NULL; } else if(!ifo_is_vts(vts_ifos[vts])) { dvd_vts[vts].valid = false; ifoClose(vts_ifos[vts]); vts_ifos[vts] = NULL; } else { dvd_vts[vts].valid = true; } } // Exit if track number requested does not exist if(opt_track_number && (arg_track_number > dvd_info.tracks)) { fprintf(stderr, "dvd_trip: Invalid track number %d\n", arg_track_number); fprintf(stderr, "dvd_trip: Valid track numbers: 1 to %u\n", dvd_info.tracks); ifoClose(vmg_ifo); DVDClose(dvdread_dvd); return 1; } else if(opt_track_number) { dvd_trip.track = arg_track_number; } uint16_t ix = 0; uint16_t track = 1; uint32_t longest_msecs = 0; for(ix = 0, track = 1; ix < dvd_info.tracks; ix++, track++) { vts = dvd_vts_ifo_number(vmg_ifo, ix + 1); vts_ifo = vts_ifos[vts]; dvd_track_info(&dvd_tracks[ix], track, vmg_ifo, vts_ifo); dvd_tracks[ix].valid = true; if(dvd_tracks[ix].msecs > longest_msecs) { dvd_info.longest_track = track; longest_msecs = dvd_tracks[ix].msecs; } } // Set the track number to rip if none is passed as an argument if(!opt_track_number) dvd_trip.track = dvd_info.longest_track; dvd_track = dvd_tracks[dvd_trip.track - 1]; // Set the proper chapter range if(opt_chapter_number) { if(arg_first_chapter > dvd_track.chapters) { dvd_trip.first_chapter = dvd_track.chapters; fprintf(stderr, "Resetting first chapter to %u\n", dvd_trip.first_chapter); } else dvd_trip.first_chapter = arg_first_chapter; if(arg_last_chapter > dvd_track.chapters) { dvd_trip.last_chapter = dvd_track.chapters; fprintf(stderr, "Resetting last chapter to %u\n", dvd_trip.last_chapter); } else dvd_trip.last_chapter = arg_last_chapter; } else { dvd_trip.first_chapter = 1; dvd_trip.last_chapter = dvd_track.chapters; } /** * File descriptors and filenames */ dvd_file_t *dvdread_vts_file = NULL; vts = dvd_vts_ifo_number(vmg_ifo, dvd_trip.track); vts_ifo = vts_ifos[vts]; // Open the VTS VOB dvdread_vts_file = DVDOpenFile(dvdread_dvd, vts, DVD_READ_TITLE_VOBS); printf("Track: %02u, Length: %s, Chapters: %02u, Cells: %02u, Audio streams: %02u, Subpictures: %02u, Filesize: %lu, Blocks: %lu\n", dvd_track.track, dvd_track.length, dvd_track.chapters, dvd_track.cells, dvd_track.audio_tracks, dvd_track.subtitles, dvd_track.filesize, dvd_track.blocks); // Check for track issues dvd_track.valid = true; if(dvd_vts[vts].valid == false) { dvd_track.valid = false; } if(dvd_track.msecs == 0) { printf(" Error: track has zero length\n"); dvd_track.valid = false; } if(dvd_track.chapters == 0) { printf(" Error: track has zero chapters\n"); dvd_track.valid = false; } if(dvd_track.cells == 0) { printf(" Error: track has zero cells\n"); dvd_track.valid = false; } if(dvd_track.valid == false) { printf("Track has been marked as invalid, quitting\n"); DVDCloseFile(dvdread_vts_file); if(vts_ifo) ifoClose(vts_ifo); if(vmg_ifo) ifoClose(vmg_ifo); if(dvdread_dvd) DVDClose(dvdread_dvd); return 1; } // MPV zero-indexes tracks sprintf(dvd_mpv_args, "dvdread://%u", dvd_trip.track - 1); const char *dvd_mpv_commands[] = { "loadfile", dvd_mpv_args, NULL }; // DVD playback using libmpv dvd_mpv = mpv_create(); // Terminal output mpv_set_option_string(dvd_mpv, "terminal", "yes"); if(!debug) mpv_set_option_string(dvd_mpv, "term-osd-bar", "yes"); if (debug) { mpv_request_log_messages(dvd_mpv, "debug"); strcpy(dvd_trip.vcodec_log_level, "full"); } else if(verbose) { mpv_request_log_messages(dvd_mpv, "v"); strcpy(dvd_trip.vcodec_log_level, "info"); } else { mpv_request_log_messages(dvd_mpv, "info"); strcpy(dvd_trip.vcodec_log_level, "info"); } /** Video **/ // Set output frames per second and color spaces based on source (NTSC or PAL) if(dvd_track_pal_video(vts_ifo)) { strcpy(dvd_trip.fps, "25"); strcpy(dvd_trip.color_opts, "color_primaries=bt470bg,color_trc=gamma28,colorspace=bt470bg"); } else { strcpy(dvd_trip.fps, "30000/1001"); strcpy(dvd_trip.color_opts, "color_primaries=smpte170m,color_trc=smpte170m,colorspace=smpte170m"); } /** Containers and Presets **/ // Set preset defaults if(strncmp(dvd_trip.container, "mkv", 3) == 0) { if(!opt_filename) strcpy(dvd_trip.filename, "trip_encode.mkv"); strcpy(dvd_trip.vcodec, "libx265"); strcpy(dvd_trip.vcodec_preset, "medium"); strcpy(dvd_trip.acodec, "libfdk_aac"); if(strncmp(dvd_trip.preset, "low", 3) == 0) { strcpy(dvd_trip.vcodec_preset, "fast"); dvd_trip.crf = 28; } else if(strncmp(dvd_trip.preset, "medium", 6) == 0) { strcpy(dvd_trip.vcodec_preset, "medium"); dvd_trip.crf = 24; } else if(strncmp(dvd_trip.preset, "high", 4) == 0) { strcpy(dvd_trip.vcodec_preset, "slow"); strcpy(dvd_trip.acodec_opts, "b=192k"); dvd_trip.crf = 20; } else if(strncmp(dvd_trip.preset, "insane", 6) == 0) { strcpy(dvd_trip.vcodec_preset, "slower"); strcpy(dvd_trip.acodec_opts, "b=256k"); dvd_trip.crf = 14; } sprintf(dvd_trip.vcodec_opts, "%s,preset=%s,crf=%u,x265-params=log-level=%s", dvd_trip.color_opts, dvd_trip.vcodec_preset, dvd_trip.crf, dvd_trip.vcodec_log_level); } if(strncmp(dvd_trip.container, "mp4", 3) == 0) { if(!opt_filename) strcpy(dvd_trip.filename, "trip_encode.mp4"); strcpy(dvd_trip.vcodec, "libx264"); strcpy(dvd_trip.vcodec_preset, "medium"); strcpy(dvd_trip.acodec, "libfdk_aac"); strcpy(dvd_trip.acodec_opts, ""); if(strncmp(dvd_trip.preset, "low", 3) == 0) { strcpy(dvd_trip.vcodec_preset, "fast"); dvd_trip.crf = 28; } else if(strncmp(dvd_trip.preset, "medium", 6) == 0) { strcpy(dvd_trip.vcodec_preset, "medium"); dvd_trip.crf = 22; } else if(strncmp(dvd_trip.preset, "high", 4) == 0) { strcpy(dvd_trip.vcodec_preset, "slow"); strcpy(dvd_trip.acodec_opts, "b=192k"); dvd_trip.crf = 20; } else if(strncmp(dvd_trip.preset, "insane", 6) == 0) { strcpy(dvd_trip.vcodec_preset, "slower"); strcpy(dvd_trip.acodec_opts, "b=256k"); dvd_trip.crf = 16; } // x264 doesn't allow passing log level (that I can see) sprintf(dvd_trip.vcodec_opts, "%s,preset=%s,crf=%u", dvd_trip.color_opts, dvd_trip.vcodec_preset, dvd_trip.crf); } if(strncmp(dvd_trip.container, "webm", 4) == 0) { if(!opt_filename) strcpy(dvd_trip.filename, "trip_encode.webm"); strcpy(dvd_trip.vcodec, "libvpx-vp9"); strcpy(dvd_trip.acodec, "libopus"); strcpy(dvd_trip.acodec_opts, "application=audio"); if(strncmp(dvd_trip.preset, "low", 3) == 0) { dvd_trip.crf = 34; sprintf(dvd_trip.vcodec_opts, "%s,b=0,crf=%u,keyint_min=0,g=360", dvd_trip.color_opts, dvd_trip.crf); strcpy(dvd_trip.acodec_opts, "application=audio,b=96000"); } if(strncmp(dvd_trip.preset, "medium", 6) == 0) { dvd_trip.crf = 32; sprintf(dvd_trip.vcodec_opts, "%s,b=0,crf=%u,keyint_min=0,g=360", dvd_trip.color_opts, dvd_trip.crf); strcpy(dvd_trip.acodec_opts, "application=audio,b=144000"); } if(strncmp(dvd_trip.preset, "high", 4) == 0) { dvd_trip.crf = 22; sprintf(dvd_trip.vcodec_opts, "%s,b=0,crf=%u,keyint_min=0,g=360", dvd_trip.color_opts, dvd_trip.crf); strcpy(dvd_trip.acodec_opts, "application=audio,b=192000"); } if(strncmp(dvd_trip.preset, "insane", 6) == 0) { dvd_trip.crf = 16; sprintf(dvd_trip.vcodec_opts, "%s,b=0,crf=%u,keyint_min=0,g=360", dvd_trip.color_opts, dvd_trip.crf); strcpy(dvd_trip.acodec_opts, "application=audio,b=256000"); } } mpv_set_option_string(dvd_mpv, "o", dvd_trip.filename); mpv_set_option_string(dvd_mpv, "ovc", dvd_trip.vcodec); mpv_set_option_string(dvd_mpv, "ovcopts", dvd_trip.vcodec_opts); mpv_set_option_string(dvd_mpv, "oac", dvd_trip.acodec); if(strlen(dvd_trip.acodec_opts) > 0) mpv_set_option_string(dvd_mpv, "oacopts", dvd_trip.acodec_opts); mpv_set_option_string(dvd_mpv, "dvd-device", device_filename); mpv_set_option_string(dvd_mpv, "track-auto-selection", "yes"); mpv_set_option_string(dvd_mpv, "input-default-bindings", "yes"); mpv_set_option_string(dvd_mpv, "input-vo-keyboard", "yes"); mpv_set_option_string(dvd_mpv, "resume-playback", "no"); // MPV's chapter range starts at the first one, and ends at the last one plus one // fex: to play chapter 1 only, mpv --start '#1' --end '#2' sprintf(dvd_mpv_first_chapter, "#%u", dvd_trip.first_chapter); sprintf(dvd_mpv_last_chapter, "#%u", dvd_trip.last_chapter + 1); mpv_set_option_string(dvd_mpv, "start", dvd_mpv_first_chapter); mpv_set_option_string(dvd_mpv, "end", dvd_mpv_last_chapter); if(strlen(dvd_trip.audio_aid) > 0) mpv_set_option_string(dvd_mpv, "aid", dvd_trip.audio_aid); else if(strlen(dvd_trip.audio_lang) > 0) mpv_set_option_string(dvd_mpv, "alang", dvd_trip.audio_lang); /** Video Filters **/ if(mpv_client_api_version() <= MPV_MAKE_VERSION(1, 25)) { // Syntax up to 0.27.2 mpv_set_option_string(dvd_mpv, "ofps", dvd_trip.fps); if(dvd_trip.detelecine && dvd_trip.deinterlace) sprintf(dvd_trip.vf_opts, "lavfi=yadif,lavfi=pullup,lavfi=dejudder"); else if(dvd_trip.deinterlace) sprintf(dvd_trip.vf_opts, "lavfi=yadif"); else if(dvd_trip.detelecine) sprintf(dvd_trip.vf_opts, "lavfi=pullup,lavfi=dejudder"); } else { // Syntax starting in 0.29.1 if(dvd_trip.detelecine && dvd_trip.deinterlace) sprintf(dvd_trip.vf_opts, "lavfi-yadif,lavfi-pullup,lavfi-dejudder,fps=%s", dvd_trip.fps); else if(dvd_trip.deinterlace) sprintf(dvd_trip.vf_opts, "lavfi-yadif,fps=%s", dvd_trip.fps); else if(dvd_trip.detelecine) sprintf(dvd_trip.vf_opts, "lavfi-pullup,lavfi-dejudder,fps=%s", dvd_trip.fps); else sprintf(dvd_trip.vf_opts, "fps=%s", dvd_trip.fps); } mpv_set_option_string(dvd_mpv, "vf", dvd_trip.vf_opts); if(dvd_trip.pass == 1) { fprintf(stderr, "dvd_trip [info]: dvd track %u\n", dvd_trip.track); fprintf(stderr, "dvd_trip [info]: chapters %u to %u\n", dvd_trip.first_chapter, dvd_trip.last_chapter); fprintf(stderr, "dvd_trip [info]: saving to %s\n", dvd_trip.filename); fprintf(stderr, "dvd_trip [info]: vcodec %s\n", dvd_trip.vcodec); fprintf(stderr, "dvd_trip [info]: acodec %s\n", dvd_trip.acodec); fprintf(stderr, "dvd_trip [info]: ovcopts %s\n", dvd_trip.vcodec_opts); fprintf(stderr, "dvd_trip [info]: oacopts %s\n", dvd_trip.acodec_opts); if(strlen(dvd_trip.vf_opts)) fprintf(stderr, "dvd_trip [info]: vf %s\n", dvd_trip.vf_opts); fprintf(stderr, "dvd_trip [info]: output fps %s\n", dvd_trip.fps); if(dvd_trip.deinterlace) fprintf(stderr, "dvd_trip [info]: deinterlacing video\n"); if(dvd_trip.detelecine) fprintf(stderr, "dvd_trip [info]: detelecining video\n"); } mpv_initialize(dvd_mpv); mpv_command(dvd_mpv, dvd_mpv_commands); while(true) { dvd_mpv_event = mpv_wait_event(dvd_mpv, -1); if(dvd_mpv_event->event_id == MPV_EVENT_SHUTDOWN || dvd_mpv_event->event_id == MPV_EVENT_END_FILE) break; // Logging output if((verbose || debug) && dvd_mpv_event->event_id == MPV_EVENT_LOG_MESSAGE) { dvd_mpv_log_message = (struct mpv_event_log_message *)dvd_mpv_event->data; printf("mpv [%s]: %s", dvd_mpv_log_message->level, dvd_mpv_log_message->text); } } mpv_terminate_destroy(dvd_mpv); DVDCloseFile(dvdread_vts_file); if(vts_ifo) ifoClose(vts_ifo); if(vmg_ifo) ifoClose(vmg_ifo); if(dvdread_dvd) DVDClose(dvdread_dvd); return 0; }
void gmpv_mpv_opt_handle_msg_level(GmpvMpv *mpv) { const struct { gchar *name; mpv_log_level level; } level_map[] = { {"no", MPV_LOG_LEVEL_NONE}, {"fatal", MPV_LOG_LEVEL_FATAL}, {"error", MPV_LOG_LEVEL_ERROR}, {"warn", MPV_LOG_LEVEL_WARN}, {"info", MPV_LOG_LEVEL_INFO}, {"v", MPV_LOG_LEVEL_V}, {"debug", MPV_LOG_LEVEL_DEBUG}, {"trace", MPV_LOG_LEVEL_TRACE}, {NULL, MPV_LOG_LEVEL_NONE} }; gchar *optbuf = NULL; gchar **tokens = NULL; mpv_log_level min_level = DEFAULT_LOG_LEVEL; gint i; optbuf = mpv_get_property_string(mpv->mpv_ctx, "options/msg-level"); if(optbuf) { tokens = g_strsplit(optbuf, ",", 0); } if(mpv->log_level_list) { g_slist_free_full(mpv->log_level_list, g_free); mpv->log_level_list = NULL; } for(i = 0; tokens && tokens[i]; i++) { gchar **pair = g_strsplit(tokens[i], "=", 2); module_log_level *level = g_malloc(sizeof(module_log_level)); gboolean found = FALSE; gint j; level->prefix = g_strdup(pair[0]); for(j = 0; level_map[j].name && !found; j++) { if(g_strcmp0(pair[1], level_map[j].name) == 0) { level->level = level_map[j].level; found = TRUE; } } /* Ignore if the given level is invalid */ if(found) { /* Lower log levels have higher values */ if(level->level > min_level) { min_level = level->level; } if(g_strcmp0(level->prefix, "all") != 0) { mpv->log_level_list = g_slist_append (mpv->log_level_list, level); } } g_strfreev(pair); } for(i = 0; level_map[i].level != min_level; i++); mpv_check_error (mpv_request_log_messages(mpv->mpv_ctx, level_map[i].name)); mpv_free(optbuf); g_strfreev(tokens); }
PlayEngine::PlayEngine() : d(new Data(this)) { Q_ASSERT(d->confDir.isValid()); _Debug("Create audio/video plugins"); d->audio = new AudioController(this); d->video = new VideoOutput(this); d->filter = new VideoFilter; d->chapterInfo = new ChapterInfoObject(this, this); d->updateMediaName(); _Debug("Make registrations and connections"); connect(d->video, &VideoOutput::formatChanged, this, &PlayEngine::updateVideoFormat); connect(d->video, &VideoOutput::droppedFramesChanged, this, &PlayEngine::droppedFramesChanged); d->handle = mpv_create(); auto verbose = qgetenv("CMPLAYER_MPV_VERBOSE").toLower().trimmed(); const QVector<QByteArray> lvs = {"no", "fatal", "error", "warn", "info", "status", "v", "debug", "trace"}; if (lvs.indexOf(verbose) < lvs.indexOf("info")) verbose = "info"; mpv_request_log_messages(d->handle, verbose.constData()); d->observe(); connect(this, &PlayEngine::beginChanged, this, &PlayEngine::endChanged); connect(this, &PlayEngine::durationChanged, this, &PlayEngine::endChanged); connect(this, &PlayEngine::videoStreamsChanged, this, [=] () { if (_Change(d->hasVideo, !d->streams[StreamVideo].tracks.isEmpty())) emit hasVideoChanged(); d->videoInfo.setTracks(d->streams[StreamVideo].tracks); }); connect(this, &PlayEngine::audioStreamsChanged, this, [=] () { d->audioInfo.setTracks(d->streams[StreamAudio].tracks); }); connect(this, &PlayEngine::subtitleStreamsChanged, this, [=] () { d->subInfo.setTracks(d->streams[StreamSubtitle].tracks); }); connect(this, &PlayEngine::currentVideoStreamChanged, this, [=] (int id) { d->videoInfo.setTrack(d->streams[StreamVideo].tracks.value(id)); }); connect(this, &PlayEngine::currentAudioStreamChanged, this, [=] (int id) { d->audioInfo.setTrack(d->streams[StreamAudio].tracks.value(id)); }); connect(this, &PlayEngine::currentSubtitleStreamChanged, this, [=] () { d->subInfo.setTracks(d->streams[StreamSubtitle].tracks); }); auto checkDeint = [=] () { auto act = Unavailable; if (d->filter->isInputInterlaced()) act = d->filter->isOutputInterlaced() ? Deactivated : Activated; d->videoInfo.setDeinterlacer(act); }; connect(d->filter, &VideoFilter::inputInterlacedChanged, this, checkDeint, Qt::QueuedConnection); connect(d->filter, &VideoFilter::outputInterlacedChanged, this, checkDeint, Qt::QueuedConnection); connect(d->audio, &AudioController::inputFormatChanged, this, [=] () { d->audioInfo.output()->setFormat(d->audio->inputFormat()); }, Qt::QueuedConnection); connect(d->audio, &AudioController::outputFormatChanged, this, [=] () { d->audioInfo.renderer()->setFormat(d->audio->outputFormat()); }, Qt::QueuedConnection); connect(d->audio, &AudioController::samplerateChanged, this, [=] (int sr) { d->audioInfo.renderer()->setSampleRate(sr, true); }, Qt::QueuedConnection); connect(d->audio, &AudioController::gainChanged, &d->audioInfo, &AudioInfoObject::setNormalizer); auto setOption = [this] (const char *name, const char *data) { const auto err = mpv_set_option_string(d->handle, name, data); d->fatal(err, "Couldn't set option %%=%%.", name, data); }; setOption("fs", "no"); setOption("input-cursor", "yes"); setOption("softvol", "yes"); setOption("softvol-max", "1000.0"); setOption("sub-auto", "no"); setOption("osd-level", "0"); setOption("quiet", "yes"); setOption("input-terminal", "no"); setOption("ad-lavc-downmix", "no"); setOption("title", "\"\""); setOption("vo", d->vo().constData()); setOption("fixed-vo", "yes"); auto overrides = qgetenv("CMPLAYER_MPV_OPTIONS").trimmed(); if (!overrides.isEmpty()) { const auto opts = QString::fromLocal8Bit(overrides); const auto args = opts.split(QRegEx(uR"([\s\t]+)"_q), QString::SkipEmptyParts); for (int i=0; i<args.size(); ++i) { if (!args[i].startsWith("--"_a)) { _Error("Cannot parse option %%.", args[i]); continue; } const auto arg = args[i].midRef(2); const int index = arg.indexOf('='_q); if (index < 0) { if (arg.startsWith("no-"_a)) setOption(arg.mid(3).toLatin1(), "no"); else setOption(arg.toLatin1(), "yes"); } else { const auto key = arg.left(index).toLatin1(); const auto value = arg.mid(index+1).toLatin1(); setOption(key, value); } } } d->fatal(mpv_initialize(d->handle), "Couldn't initialize mpv."); _Debug("Initialized"); d->initialized = true; }
int main(int argc, char **argv) { /** * Parse options */ bool verbose = false; bool debug = false; bool opt_track_number = false; bool opt_chapter_number = false; bool opt_widescreen = false; bool opt_pan_scan = false; bool opt_no_video = false; bool opt_no_audio = false; uint16_t arg_track_number = 0; uint8_t arg_first_chapter = 1; uint8_t arg_last_chapter = 99; struct dvd_player dvd_player; struct dvd_playback dvd_playback; char dvd_mpv_args[13] = {'\0'}; mpv_handle *dvd_mpv = NULL; mpv_event *dvd_mpv_event = NULL; struct mpv_event_log_message *dvd_mpv_log_message = NULL; const char *home_dir = getenv("HOME"); const char *lang = getenv("LANG"); // Video Title Set struct dvd_vts dvd_vts[99]; // DVD player default options snprintf(dvd_player.config_dir, 20, "/.config/dvd_player"); memset(dvd_player.mpv_config_dir, '\0', sizeof(dvd_player.mpv_config_dir)); if(home_dir != NULL) snprintf(dvd_player.mpv_config_dir, PATH_MAX - 1, "%s%s", home_dir, dvd_player.config_dir); // DVD playback default options dvd_playback.track = 1; dvd_playback.first_chapter = 1; dvd_playback.last_chapter = 99; dvd_playback.fullscreen = false; dvd_playback.deinterlace = false; dvd_playback.subtitles = false; snprintf(dvd_playback.mpv_chapters_range, 8, "%u-%u", 1, 99); memset(dvd_playback.audio_lang, '\0', sizeof(dvd_playback.audio_lang)); if(strlen(lang) >= 2) snprintf(dvd_playback.audio_lang, 3, "%s", strndup(lang, 2)); memset(dvd_playback.audio_aid, '\0', sizeof(dvd_playback.audio_aid)); memset(dvd_playback.subtitles_lang, '\0', sizeof(dvd_playback.subtitles_lang)); if(strlen(lang) >= 2) snprintf(dvd_playback.subtitles_lang, 3, "%s", strndup(lang, 2)); memset(dvd_playback.subtitles_sid, '\0', sizeof(dvd_playback.subtitles_sid)); const char str_options[] = "Aa:c:dfhpSs:t:Vvwz"; struct option long_options[] = { { "track", required_argument, 0, 't' }, { "chapters", required_argument, 0, 'c' }, { "fullscreen", no_argument, 0, 'f' }, { "deinterlace", no_argument, 0, 'd' }, { "alang", required_argument, 0, 'a' }, { "slang", required_argument, 0, 's' }, { "aid", required_argument, 0, 'A' }, { "sid", required_argument, 0, 'S' }, { "help", no_argument, 0, 'h' }, { "version", no_argument, 0, 'V' }, { "widescreen", no_argument, 0, 'w' }, { "pan-scan", no_argument, 0, 'p' }, { "verbose", no_argument, 0, 'v' }, { "debug", no_argument, 0, 'z' }, { 0, 0, 0, 0 } }; int long_index = 0; int opt = 0; opterr = 1; char *token = NULL; while((opt = getopt_long(argc, argv, str_options, long_options, &long_index )) != -1) { switch(opt) { case 'A': strncpy(dvd_playback.audio_aid, optarg, 3); break; case 'a': strncpy(dvd_playback.audio_lang, optarg, 2); break; case 'c': opt_chapter_number = true; token = strtok(optarg, "-"); { if(strlen(token) > 2) { fprintf(stderr, "Chapter range must be between 1 and 99\n"); return 1; } arg_first_chapter = (uint8_t)strtoumax(token, NULL, 0); } token = strtok(NULL, "-"); if(token != NULL) { if(strlen(token) > 2) { fprintf(stderr, "Chapter range must be between 1 and 99\n"); return 1; } arg_last_chapter = (uint8_t)strtoumax(token, NULL, 0); } if(arg_first_chapter == 0) arg_first_chapter = 1; if(arg_last_chapter < arg_first_chapter) arg_last_chapter = arg_first_chapter; if(arg_first_chapter > arg_last_chapter) arg_first_chapter = arg_last_chapter; break; case 'd': dvd_playback.deinterlace = true; break; case 'f': dvd_playback.fullscreen = true; break; case 'h': print_usage(DVD_INFO_PROGRAM); return 0; case 'p': opt_pan_scan = true; break; case 's': strncpy(dvd_playback.subtitles_lang, optarg, 2); dvd_playback.subtitles = true; break; case 'S': strncpy(dvd_playback.subtitles_sid, optarg, 3); dvd_playback.subtitles = true; break; case 't': opt_track_number = true; arg_track_number = (uint16_t)strtoumax(optarg, NULL, 0); break; case 'V': print_version("dvd_player"); return 0; case 'v': verbose = true; break; case 'w': opt_widescreen = true; break; case 'z': verbose = true; debug = true; break; // ignore unknown arguments case '?': print_usage("dvd_player"); return 1; // let getopt_long set the variable case 0: default: break; } } if(opt_pan_scan && opt_widescreen) opt_pan_scan = false; const char *device_filename = DEFAULT_DVD_DEVICE; if (argv[optind]) device_filename = argv[optind]; if(access(device_filename, F_OK) != 0) { fprintf(stderr, "cannot access %s\n", device_filename); return 1; } // Check to see if device can be opened int dvd_fd = 0; dvd_fd = dvd_device_open(device_filename); if(dvd_fd < 0) { fprintf(stderr, "dvd_player: error opening %s\n", device_filename); return 1; } dvd_device_close(dvd_fd); #ifdef __linux__ // Poll drive status if it is hardware if(dvd_device_is_hardware(device_filename)) { // Wait for the drive to become ready if(!dvd_drive_has_media(device_filename)) { fprintf(stderr, "drive status: "); dvd_drive_display_status(device_filename); return 1; } } #endif dvd_reader_t *dvdread_dvd = NULL; dvdread_dvd = DVDOpen(device_filename); if(!dvdread_dvd) { fprintf(stderr, "* dvdread could not open %s\n", device_filename); return 1; } ifo_handle_t *vmg_ifo = NULL; vmg_ifo = ifoOpen(dvdread_dvd, 0); if(vmg_ifo == NULL) { fprintf(stderr, "* Could not open IFO zero\n"); DVDClose(dvdread_dvd); return 1; } // DVD struct dvd_info dvd_info; memset(dvd_info.dvdread_id, '\0', sizeof(dvd_info.dvdread_id)); dvd_info.video_title_sets = dvd_video_title_sets(vmg_ifo); dvd_info.side = 1; memset(dvd_info.title, '\0', sizeof(dvd_info.title)); memset(dvd_info.provider_id, '\0', sizeof(dvd_info.provider_id)); memset(dvd_info.vmg_id, '\0', sizeof(dvd_info.vmg_id)); dvd_info.tracks = dvd_tracks(vmg_ifo); dvd_info.longest_track = 1; dvd_title(dvd_info.title, device_filename); printf("Disc title: %s\n", dvd_info.title); uint16_t num_ifos = 1; num_ifos = vmg_ifo->vts_atrt->nr_of_vtss; if(num_ifos < 1) { fprintf(stderr, "* DVD has no title IFOs?!\n"); fprintf(stderr, "* Most likely a bug in libdvdread or a bad master or problems reading the disc\n"); ifoClose(vmg_ifo); DVDClose(dvdread_dvd); return 1; } // Track struct dvd_track dvd_track; memset(&dvd_track, 0, sizeof(dvd_track)); struct dvd_track dvd_tracks[DVD_MAX_TRACKS]; memset(&dvd_tracks, 0, sizeof(dvd_track) * dvd_info.tracks); // Open first IFO uint16_t vts = 1; ifo_handle_t *vts_ifo = NULL; vts_ifo = ifoOpen(dvdread_dvd, vts); if(vts_ifo == NULL) { fprintf(stderr, "* Could not open VTS_IFO for track %u\n", 1); return 1; } ifoClose(vts_ifo); vts_ifo = NULL; // Create an array of all the IFOs ifo_handle_t *vts_ifos[DVD_MAX_VTS_IFOS]; vts_ifos[0] = NULL; for(vts = 1; vts < dvd_info.video_title_sets + 1; vts++) { dvd_vts[vts].vts = vts; dvd_vts[vts].valid = false; dvd_vts[vts].blocks = 0; dvd_vts[vts].filesize = 0; dvd_vts[vts].vobs = 0; dvd_vts[vts].tracks = 0; dvd_vts[vts].valid_tracks = 0; dvd_vts[vts].invalid_tracks = 0; vts_ifos[vts] = ifoOpen(dvdread_dvd, vts); if(vts_ifos[vts] == NULL) { dvd_vts[vts].valid = false; vts_ifos[vts] = NULL; } else if(!ifo_is_vts(vts_ifos[vts])) { dvd_vts[vts].valid = false; ifoClose(vts_ifos[vts]); vts_ifos[vts] = NULL; } else { dvd_vts[vts].valid = true; } } // Exit if track number requested does not exist if(opt_track_number && (arg_track_number > dvd_info.tracks)) { fprintf(stderr, "dvd_player: Invalid track number %d\n", arg_track_number); fprintf(stderr, "dvd_player: Valid track numbers: 1 to %u\n", dvd_info.tracks); ifoClose(vmg_ifo); DVDClose(dvdread_dvd); return 1; } else if(opt_track_number) { dvd_playback.track = arg_track_number; } uint16_t ix = 0; uint16_t track = 1; uint32_t longest_msecs = 0; uint16_t longest_widescreen_track = 0; uint32_t longest_widescreen_msecs = 0; uint16_t longest_pan_scan_track = 0; uint32_t longest_pan_scan_msecs = 0; for(ix = 0, track = 1; ix < dvd_info.tracks; ix++, track++) { vts = dvd_vts_ifo_number(vmg_ifo, ix + 1); vts_ifo = vts_ifos[vts]; dvd_track_info(&dvd_tracks[ix], track, vmg_ifo, vts_ifo); if(dvd_tracks[ix].msecs > longest_msecs) { dvd_info.longest_track = track; longest_msecs = dvd_tracks[ix].msecs; } if(dvd_track_aspect_ratio_16x9(vts_ifo) && dvd_tracks[ix].msecs > longest_widescreen_msecs) { longest_widescreen_msecs = dvd_tracks[ix].msecs; longest_widescreen_track = track; } else if(dvd_track_aspect_ratio_4x3(vts_ifo) && dvd_tracks[ix].msecs > longest_pan_scan_msecs) { longest_pan_scan_msecs = dvd_tracks[ix].msecs; longest_pan_scan_track = track; } } if(opt_widescreen && longest_widescreen_track != 0) dvd_info.longest_track = longest_widescreen_track; else if(opt_pan_scan && longest_pan_scan_track != 0) dvd_info.longest_track = longest_pan_scan_track; // TODO if there is another track that is active, within ~1 seconds of longest track, and // the first is pan & scan, and the second is widescreen, switch to the widescreen one. // A more intelligent search might also see if the second one has audio tracks. Need to // find a reference DVD. // Set the track number to play if none is passed as an argument if(!opt_track_number) dvd_playback.track = dvd_info.longest_track; dvd_track = dvd_tracks[dvd_playback.track - 1]; // Set the proper chapter range if(opt_chapter_number) { if(arg_first_chapter > dvd_track.chapters) { dvd_playback.first_chapter = dvd_track.chapters; } else dvd_playback.first_chapter = arg_first_chapter; if(arg_last_chapter > dvd_track.chapters) { dvd_playback.last_chapter = dvd_track.chapters; } else dvd_playback.last_chapter = arg_last_chapter; } else { dvd_playback.first_chapter = 1; dvd_playback.last_chapter = dvd_track.chapters; } /** * File descriptors and filenames */ dvd_file_t *dvdread_vts_file = NULL; vts = dvd_vts_ifo_number(vmg_ifo, dvd_playback.track); vts_ifo = vts_ifos[vts]; // Open the VTS VOB dvdread_vts_file = DVDOpenFile(dvdread_dvd, vts, DVD_READ_TITLE_VOBS); printf("Track: %02u, Length: %s, Chapters: %02u, Cells: %02u, Audio streams: %02u, Subpictures: %02u, Filesize: %lu, Blocks: %lu\n", dvd_track.track, dvd_track.length, dvd_track.chapters, dvd_track.cells, dvd_track.audio_tracks, dvd_track.subtitles, dvd_track.filesize, dvd_track.blocks); // Check for track issues dvd_track.valid = true; if(dvd_vts[vts].valid == false) { dvd_track.valid = false; } if(dvd_track.msecs == 0) { printf(" Error: track has zero length\n"); dvd_track.valid = false; } if(dvd_track.chapters == 0) { printf(" Error: track has zero chapters\n"); dvd_track.valid = false; } if(dvd_track.cells == 0) { printf(" Error: track has zero cells\n"); dvd_track.valid = false; } if(dvd_track.valid == false) { printf("Track has been marked as invalid, quitting\n"); DVDCloseFile(dvdread_vts_file); if(vts_ifo) ifoClose(vts_ifo); if(vmg_ifo) ifoClose(vmg_ifo); if(dvdread_dvd) DVDClose(dvdread_dvd); return 1; } // DVD playback using libmpv dvd_mpv = mpv_create(); // Terminal output mpv_set_option_string(dvd_mpv, "terminal", "yes"); mpv_set_option_string(dvd_mpv, "term-osd-bar", "yes"); if(debug) { mpv_request_log_messages(dvd_mpv, "debug"); } else if(verbose) { mpv_request_log_messages(dvd_mpv, "v"); } else { mpv_request_log_messages(dvd_mpv, "none"); // Skip "[ffmpeg/audio] ac3: frame sync error" which are normal when seeking on DVDs mpv_set_option_string(dvd_mpv, "msg-level", "ffmpeg/audio=none"); } // mpv zero-indexes tracks snprintf(dvd_mpv_args, 13, "dvdread://%u", dvd_playback.track - 1); // MPV uses zero-indexing for tracks, dvd_info uses one instead const char *dvd_mpv_commands[] = { "loadfile", dvd_mpv_args, NULL }; // Load user's mpv configuration in ~/.config/dvd_player/mpv.conf (and friends) if(strlen(dvd_player.mpv_config_dir) > 0) { mpv_set_option_string(dvd_mpv, "config-dir", dvd_player.mpv_config_dir); mpv_set_option_string(dvd_mpv, "config", "yes"); } // When choosing a chapter range, mpv will add 1 to the last one requested snprintf(dvd_playback.mpv_chapters_range, 8, "%u-%u", dvd_playback.first_chapter, dvd_playback.last_chapter + 1); // Playback options and default configuration mpv_set_option_string(dvd_mpv, "dvd-device", device_filename); if(strlen(dvd_info.title) > 0) mpv_set_option_string(dvd_mpv, "title", dvd_info.title); else mpv_set_option_string(dvd_mpv, "title", "dvd_player"); mpv_set_option_string(dvd_mpv, "chapter", dvd_playback.mpv_chapters_range); mpv_set_option_string(dvd_mpv, "input-default-bindings", "yes"); mpv_set_option_string(dvd_mpv, "input-vo-keyboard", "yes"); if(strlen(dvd_playback.audio_aid) > 0) mpv_set_option_string(dvd_mpv, "aid", dvd_playback.audio_aid); else if(strlen(dvd_playback.audio_lang) > 0) mpv_set_option_string(dvd_mpv, "alang", dvd_playback.audio_lang); if(dvd_playback.subtitles && strlen(dvd_playback.subtitles_sid) > 0) mpv_set_option_string(dvd_mpv, "sid", dvd_playback.subtitles_sid); else if(dvd_playback.subtitles && strlen(dvd_playback.subtitles_lang) > 0) mpv_set_option_string(dvd_mpv, "slang", dvd_playback.subtitles_lang); if(dvd_playback.fullscreen) mpv_set_option_string(dvd_mpv, "fullscreen", NULL); if(dvd_playback.deinterlace) mpv_set_option_string(dvd_mpv, "deinterlace", "yes"); if(opt_no_video) mpv_set_option_string(dvd_mpv, "video", "no"); if(opt_no_audio) mpv_set_option_string(dvd_mpv, "audio", "no"); // start mpv mpv_initialize(dvd_mpv); mpv_command(dvd_mpv, dvd_mpv_commands); while(true) { dvd_mpv_event = mpv_wait_event(dvd_mpv, -1); // Goodbye :) if(dvd_mpv_event->event_id == MPV_EVENT_SHUTDOWN || dvd_mpv_event->event_id == MPV_EVENT_END_FILE) break; if(debug && dvd_mpv_event->event_id != MPV_EVENT_LOG_MESSAGE) printf("dvd_player [mpv_event_name]: %s\n", mpv_event_name(dvd_mpv_event->event_id)); // Logging output if((verbose || debug) && dvd_mpv_event->event_id == MPV_EVENT_LOG_MESSAGE) { dvd_mpv_log_message = (struct mpv_event_log_message *)dvd_mpv_event->data; printf("mpv [%s]: %s", dvd_mpv_log_message->level, dvd_mpv_log_message->text); } } mpv_terminate_destroy(dvd_mpv); DVDCloseFile(dvdread_vts_file); if(vts_ifo) ifoClose(vts_ifo); if(vmg_ifo) ifoClose(vmg_ifo); if(dvdread_dvd) DVDClose(dvdread_dvd); return 0; }