static gboolean handle_mpv_events(void *data) { struct plugin_context *ctx = data; if (!ctx->helper) return FALSE; while (1) { mpv_event *event = mpv_wait_event(ctx->helper->mpv, 0); if (event->event_id == MPV_EVENT_NONE) break; printf("event: %s\n", mpv_event_name(event->event_id)); if (event->event_id == MPV_EVENT_PROPERTY_CHANGE) { mpv_event_property *prop = event->data; if (prop->format == MPV_FORMAT_INT64) gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ctx->pbar), *(int64_t*)prop->data / 100.0); } if (event->event_id == MPV_EVENT_SHUTDOWN) { mpv_gtk_helper_context_destroy(ctx->helper); ctx->helper = NULL; break; } } return FALSE; }
void VideoPlayer::on_mpv_events() { while(mpv) { mpv_event *event = mpv_wait_event(mpv, 0); if(event->event_id == MPV_EVENT_NONE) break; handle_mpv_event(event); } }
void PlayerComponent::handleMpvEvents() { // Process all events, until the event queue is empty. while (1) { mpv_event *event = mpv_wait_event(m_mpv, 0); if (event->event_id == MPV_EVENT_NONE) break; handleMpvEvent(event); } }
bool DanmakuDelayGetter::event(QEvent *e) { if (e->type() != QEvent::User) return QObject::event(e); while (mpv) { mpv_event *event = mpv_wait_event(mpv, 0); if (event == NULL || event->event_id == MPV_EVENT_NONE) break; switch (event->event_id) { case MPV_EVENT_PROPERTY_CHANGE: { mpv_event_property *prop = (mpv_event_property*) event->data; if (prop->data == NULL) break; if (QByteArray(prop->name) == "duration") { double len = *(double*) prop->data; if (len > 0.5) { delay += len; start(); } } break; } case MPV_EVENT_IDLE: if (urls.isEmpty()) // Finished deleteLater(); break; case MPV_EVENT_END_FILE: // Error { mpv_event_end_file *ef = static_cast<mpv_event_end_file*>(event->data); if (ef->error == MPV_ERROR_LOADING_FAILED) { qDebug("Parse danmaku's delay failed."); while (!names.isEmpty()) { if (download) downloader->addTask(urls.takeFirst().toUtf8(), names.takeFirst(), true); else playlist->addFile(names.takeFirst(), urls.takeFirst()); } } break; } default: break; } } return true; }
void MpvWidget::on_mpv_events() { // Process all events, until the event queue is empty. while (mpv) { mpv_event *event = mpv_wait_event(mpv, 0); if (event->event_id == MPV_EVENT_NONE) { break; } handle_mpv_event(event); } }
bool MainWindow::event(QEvent *event) { // QEvent::User is sent by wakeup(). if (event->type() == QEvent::User) { // Process all events, until the event queue is empty. while (mpv) { mpv_event *event = mpv_wait_event(mpv, 0); if (event->event_id == MPV_EVENT_NONE) break; handle_mpv_event(event); } return true; } return QMainWindow::event(event); }
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; }
/** * Process various events triggered by mpv, such as printing log messages. * * \param event_block Wait until the mpv triggers specified event. Should be * NULL if no wait is required. */ static void process_mpv_events(mpv_event_id event_block) { do { mpv_event *mp_event = mpv_wait_event(mpv, 0); if(event_block == MPV_EVENT_NONE && mp_event->event_id == MPV_EVENT_NONE) break; if(mp_event->event_id == event_block) event_block = MPV_EVENT_NONE; if(mp_event->event_id == MPV_EVENT_LOG_MESSAGE) { struct mpv_event_log_message *msg = (struct mpv_event_log_message *)mp_event->data; log_cb(RETRO_LOG_INFO, "mpv: [%s] %s: %s", msg->prefix, msg->level, msg->text); } else if(mp_event->event_id == MPV_EVENT_END_FILE) { struct mpv_event_end_file *eof = (struct mpv_event_end_file *)mp_event->data; if(eof->reason == MPV_END_FILE_REASON_EOF) CORE_PREFIX(environ_cb)(RETRO_ENVIRONMENT_SHUTDOWN, NULL); #if 0 /* The following could be done instead if the file was not * closed once the end was reached - allowing the user to seek * back without reopening the file. */ struct retro_message ra_msg = { "Finished playing file", 60 * 5, /* 5 seconds */ }; CORE_PREFIX(environ_cb)(RETRO_ENVIRONMENT_SET_MESSAGE, &ra_msg);RETRO_ENVIRONMENT_SHUTDOWN #endif } else if(mp_event->event_id == MPV_EVENT_NONE) continue; else { log_cb(RETRO_LOG_INFO, "mpv: %s\n", mpv_event_name(mp_event->event_id)); } }
static gboolean mpv_event_cb(gpointer udata) { GtPlayerBackendMpvOpenGL* self = GT_PLAYER_BACKEND_MPV_OPENGL(udata); GtPlayerBackendMpvOpenGLPrivate* priv = gt_player_backend_mpv_opengl_get_instance_private(self); gboolean done = FALSE; while (!done) { mpv_event* evt = mpv_wait_event(priv->mpv, 0); switch (evt->event_id) { case MPV_EVENT_PROPERTY_CHANGE: { mpv_event_property* prop = evt->data; if (g_strcmp0(prop->name, "volume") == 0) { if (prop->data) priv->volume = *((double*) prop->data) / 100.0; g_object_notify_by_pspec(G_OBJECT(self), props[PROP_VOLUME]); } else if (g_strcmp0(prop->name, "cache-buffering-state") == 0) { if (prop->data) priv->buffer_fill = *((gint64*) prop->data) / 100.0; g_object_notify_by_pspec(G_OBJECT(self), props[PROP_BUFFER_FILL]); } break; } case MPV_EVENT_NONE: done = TRUE; break; default: break; } } priv->mpv_event_cb_id = 0; return G_SOURCE_REMOVE; }
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-x11-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)); // Play this file. const char *cmd[] = {"loadfile", argv[1], 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); 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 KNMusicBackendMpvThread::event(QEvent *event) { //Check out the event type. if(event->type()==QEvent::User) { //Get all the event until there no event. while(m_mpvHandle) { //Get the mpv event. mpv_event *mpvEvent=mpv_wait_event(m_mpvHandle, 0); //Check out the event pointer. if(mpvEvent==nullptr || MPV_EVENT_NONE == mpvEvent->event_id) { //All the event has been processed. break; } //Check out whether there's an error occurs. if(mpvEvent->error < 0) { //!FIXME: Do something here. } switch(mpvEvent->event_id) { case MPV_EVENT_PROPERTY_CHANGE: { //Cast the mpv event data to event property. mpv_event_property *prop=(mpv_event_property *)mpvEvent->data; //Check out the property name. // From bakamplayer: // playback-time does the same thing as time-pos but works for // streaming media if(strcmp(prop->name, "time-pos") == 0) { //Cehck the format. if(MPV_FORMAT_DOUBLE==prop->format) { //Output the playback time. emit positionChanged((qint64)((*(double *)prop->data)*1000.0)-m_startPosition); } } //Mission finished. break; } //This event is totally different, though I don't know why. //BakaMPlayer says it is. case MPV_EVENT_START_FILE: { break; } case MPV_EVENT_FILE_LOADED: { //Pause it right after the file loaded. pause(); //Reset the playing state. m_state=MusicUtil::Stopped; //Emit the state changed signal. emit stateChanged(m_state); //We have to update all the file inforamtion here. //Get the duration of the file. double mediaDuration; //Get the property. mpv_get_property(m_mpvHandle, "length", MPV_FORMAT_DOUBLE, &mediaDuration); //Save the total duration. qint64 propertyDuration=(qint64)(mediaDuration*1000.0); //Check property duration if(propertyDuration>0) { //For valid duration. m_totalDuration=propertyDuration; //Update the end position. checkStartAndEndPosition(); //Emit the loaded signal. emit loadSuccess(); } //Mission complete. break; } case MPV_EVENT_PAUSE: { //We should save the state as pause. //We may never have stop. m_state=MusicUtil::Paused; //Emit the state changed signal. emit stateChanged(m_state); break; } case MPV_EVENT_UNPAUSE: { //We should save the state as playing. m_state=MusicUtil::Playing; //Emit the state changed signal. emit stateChanged(m_state); //Sync the playing volume. setVolume(m_volumeSize); break; } case MPV_EVENT_END_FILE: { //It should be stopped. m_state=MusicUtil::Stopped; //Emit the state changed signal. emit stateChanged(m_state); break; } } } } //For others, do the defualt event. return KNMusicStandardBackendThread::event(event); }
bool MpvHandler::event(QEvent *event) { if(event->type() == QEvent::User) { while(mpv) { mpv_event *event = mpv_wait_event(mpv, 0); if(event == nullptr || event->event_id == MPV_EVENT_NONE) { break; } HandleErrorCode(event->error); switch (event->event_id) { case MPV_EVENT_PROPERTY_CHANGE: { mpv_event_property *prop = (mpv_event_property*)event->data; if(QString(prop->name) == "playback-time") // playback-time does the same thing as time-pos but works for streaming media { if(prop->format == MPV_FORMAT_DOUBLE) { setTime((int)*(double*)prop->data); lastTime = time; } } else if(QString(prop->name) == "volume") { if(prop->format == MPV_FORMAT_DOUBLE) setVolume((int)*(double*)prop->data); } else if(QString(prop->name) == "sid") { if(prop->format == MPV_FORMAT_INT64) setSid(*(int*)prop->data); } else if(QString(prop->name) == "aid") { if(prop->format == MPV_FORMAT_INT64) setAid(*(int*)prop->data); } else if(QString(prop->name) == "sub-visibility") { if(prop->format == MPV_FORMAT_FLAG) setSubtitleVisibility((bool)*(unsigned*)prop->data); } else if(QString(prop->name) == "mute") { if(prop->format == MPV_FORMAT_FLAG) setMute((bool)*(unsigned*)prop->data); } else if(QString(prop->name) == "core-idle") { if(prop->format == MPV_FORMAT_FLAG) { if((bool)*(unsigned*)prop->data && playState == Mpv::Playing) ShowText(tr("Buffering..."), 0); else ShowText(QString(), 0); } } else if(QString(prop->name) == "paused-for-cache") { if(prop->format == MPV_FORMAT_FLAG) { if((bool)*(unsigned*)prop->data && playState == Mpv::Playing) ShowText(tr("Your network is slow or stuck, please wait a bit"), 0); else ShowText(QString(), 0); } } break; } case MPV_EVENT_IDLE: fileInfo.length = 0; setTime(0); setPlayState(Mpv::Idle); break; // these two look like they're reversed but they aren't. the names are misleading. case MPV_EVENT_START_FILE: setPlayState(Mpv::Loaded); break; case MPV_EVENT_FILE_LOADED: setPlayState(Mpv::Started); LoadFileInfo(); SetProperties(); case MPV_EVENT_UNPAUSE: setPlayState(Mpv::Playing); break; case MPV_EVENT_PAUSE: setPlayState(Mpv::Paused); ShowText(QString(), 0); break; case MPV_EVENT_END_FILE: if(playState == Mpv::Loaded) ShowText(tr("File couldn't be opened")); setPlayState(Mpv::Stopped); break; case MPV_EVENT_SHUTDOWN: QCoreApplication::quit(); break; case MPV_EVENT_LOG_MESSAGE: { mpv_event_log_message *message = static_cast<mpv_event_log_message*>(event->data); if(message != nullptr) emit messageSignal(message->text); break; } default: // unhandled events break; } } return true; } return QObject::event(event); }
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; }
static gboolean mpv_event_handler(gpointer data) { GmpvMpvObj *mpv = data; gboolean done = !mpv; while(!done) { mpv_event *event = mpv->mpv_ctx? mpv_wait_event(mpv->mpv_ctx, 0): NULL; if(!event) { done = TRUE; } else if(event->event_id == MPV_EVENT_PROPERTY_CHANGE) { mpv_event_property *prop = event->data; mpv_prop_change_handler(mpv, prop); g_signal_emit_by_name( mpv, "mpv-prop-change", prop->name ); } else if(event->event_id == MPV_EVENT_IDLE) { if(mpv->state.loaded) { mpv->state.loaded = FALSE; gmpv_mpv_obj_set_property_flag (mpv, "pause", TRUE); gmpv_playlist_reset(mpv->playlist); } mpv->state.init_load = FALSE; } else if(event->event_id == MPV_EVENT_FILE_LOADED) { mpv->state.loaded = TRUE; mpv->state.init_load = FALSE; mpv_obj_update_playlist(mpv); } else if(event->event_id == MPV_EVENT_END_FILE) { mpv_event_end_file *ef_event = event->data; mpv->state.init_load = FALSE; if(mpv->state.loaded) { mpv->state.new_file = FALSE; } if(ef_event->reason == MPV_END_FILE_REASON_ERROR) { const gchar *err; gchar *msg; err = mpv_error_string(ef_event->error); msg = g_strdup_printf ( _("Playback was terminated " "abnormally. Reason: %s."), err ); gmpv_mpv_obj_set_property_flag (mpv, "pause", TRUE); g_signal_emit_by_name(mpv, "mpv-error", msg); g_free(msg); } } else if(event->event_id == MPV_EVENT_VIDEO_RECONFIG) { if(mpv->state.new_file) { gmpv_mpv_opt_handle_autofit(mpv); } } else if(event->event_id == MPV_EVENT_PLAYBACK_RESTART) { g_signal_emit_by_name(mpv, "mpv-playback-restart"); } else if(event->event_id == MPV_EVENT_LOG_MESSAGE) { mpv_obj_log_handler(mpv, event->data); } else if(event->event_id == MPV_EVENT_SHUTDOWN || event->event_id == MPV_EVENT_NONE) { done = TRUE; } if(event) { if(mpv->event_callback) { mpv->event_callback (event, mpv->event_callback_data); } if(mpv->mpv_ctx) { g_signal_emit_by_name (mpv, "mpv-event", event->event_id); } else { done = TRUE; } } } return FALSE; }
void MpvAudioOutput::event_loop() { while (true) { auto event = mpv_wait_event(handle_, -1); // qDebug() << "mpv event " << mpv_event_name(event->event_id); switch (event->event_id) { case MPV_EVENT_SHUTDOWN: return; case MPV_EVENT_QUEUE_OVERFLOW: qWarning() << "mpv queue overflow"; break; case MPV_EVENT_START_FILE: setState(AudioState::Buffering); break; case MPV_EVENT_FILE_LOADED: setState(AudioState::Playing); emit currentSourceChanged(); setVolume(); if (seek_offset_ != -1) { seek(seek_offset_); seek_offset_ = -1; } break; case MPV_EVENT_END_FILE: { auto end_ev = reinterpret_cast<mpv_event_end_file *>(event->data); if (end_ev->reason == MPV_END_FILE_REASON_ERROR) qWarning() << "Ended file: " << mpv_error_string(end_ev->error); break; } case MPV_EVENT_LOG_MESSAGE: { auto log = reinterpret_cast<mpv_event_log_message *>(event->data); qDebug() << "mpv [" << log->prefix << "] " << log->text; break; } case MPV_EVENT_PROPERTY_CHANGE: { auto prop = reinterpret_cast<mpv_event_property *>(event->data); if (prop->format != MPV_FORMAT_NONE && prop->data) { if (std::string(prop->name) == "playback-time") { std::string pos(*(reinterpret_cast<char **>(prop->data))); emit tick(pos_to_qint64(pos)); if (volumeNeverSet_) setVolume(); } else if (std::string(prop->name) == "idle") { int idle = *reinterpret_cast<int *>(prop->data); if (idle) { setState(AudioState::Stopped); emit finished(); } else setState(AudioState::Playing); } else if (std::string(prop->name) == "pause") { int pause = *reinterpret_cast<int *>(prop->data); if (pause) setState(AudioState::Paused); else if (state_ == AudioState::Paused) setState(AudioState::Playing); } else if (std::string(prop->name) == "duration") { double v = *reinterpret_cast<double *>(prop->data); emit durationChanged(v); } else if (std::string(prop->name) == "metadata") { emit metadataChanged(get_property("media-title").toString(), get_property("audio-format").toString(), get_property("audio-params/samplerate").toInt()); } } break; } default: break; } } }
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; }