static struct mp_image *frame_queue_pop(struct mpv_opengl_cb_context *ctx) { if (ctx->queued_frames == 0) return NULL; struct mp_image *ret = ctx->frame_queue[0]; MP_TARRAY_REMOVE_AT(ctx->frame_queue, ctx->queued_frames, 0); return ret; }
static struct vo_frame *frame_queue_pop(struct mpv_opengl_cb_context *ctx) { if (ctx->queued_frames == 0) return NULL; struct vo_frame *ret = ctx->frame_queue[0]; MP_TARRAY_REMOVE_AT(ctx->frame_queue, ctx->queued_frames, 0); pthread_cond_broadcast(&ctx->wakeup); return ret; }
// Select a decoder from the given list for the given codec. The selection // can be influenced by the selection string, which can specify a priority // list of preferred decoders. // This returns a list of decoders to try, with the preferred decoders first. // The selection string corresponds to --vd/--ad directly, and has the // following syntax: // selection = [<entry> ("," <entry>)*] // entry = <family> ":" <decoder> // prefer decoder // entry = <family> ":*" // prefer all decoders // entry = "+" <family> ":" <decoder> // force a decoder // entry = "-" <family> ":" <decoder> // exclude a decoder // entry = "-" // don't add fallback decoders // Forcing a decoder means it's added even if the codec mismatches. struct mp_decoder_list *mp_select_decoders(struct mp_decoder_list *all, const char *codec, const char *selection) { struct mp_decoder_list *list = talloc_zero(NULL, struct mp_decoder_list); struct mp_decoder_list *remove = talloc_zero(NULL, struct mp_decoder_list); if (!codec) codec = "unknown"; bool stop = false; bstr sel = bstr0(selection); while (sel.len) { bstr entry; bstr_split_tok(sel, ",", &entry, &sel); if (bstr_equals0(entry, "-")) { stop = true; break; } bool force = bstr_eatstart0(&entry, "+"); bool exclude = !force && bstr_eatstart0(&entry, "-"); struct mp_decoder_list *dest = exclude ? remove : list; bstr family, decoder; if (!bstr_split_tok(entry, ":", &family, &decoder)) { mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Decoders must be specified as " "'family:decoder' for the --ad/--vd options.\n"); break; } if (bstr_equals0(decoder, "*")) { for (int n = 0; n < all->num_entries; n++) { struct mp_decoder_entry *cur = &all->entries[n]; if (bstr_equals0(family, cur->family)) add_new(dest, cur, codec); } } else { add_new(dest, find_decoder(all, family, decoder), force ? NULL : codec); } } if (!stop) { // Add the remaining codecs which haven't been added yet for (int n = 0; n < all->num_entries; n++) add_new(list, &all->entries[n], codec); } for (int n = 0; n < remove->num_entries; n++) { struct mp_decoder_entry *ex = &remove->entries[n]; struct mp_decoder_entry *del = find_decoder(list, bstr0(ex->family), bstr0(ex->decoder)); if (del) { int index = del - &list->entries[0]; MP_TARRAY_REMOVE_AT(list->entries, list->num_entries, index); } } talloc_free(remove); return list; }
static void remove_intersecting_rcs(struct mp_rect *list, int *count) { int M = MERGE_RC_PIXELS; bool changed = true; while (changed) { changed = false; for (int a = 0; a < *count; a++) { struct mp_rect *rc_a = &list[a]; for (int b = *count - 1; b > a; b--) { struct mp_rect *rc_b = &list[b]; if (rc_a->x0 - M <= rc_b->x1 && rc_a->x1 + M >= rc_b->x0 && rc_a->y0 - M <= rc_b->y1 && rc_a->y1 + M >= rc_b->y0) { mp_rect_union(rc_a, rc_b); MP_TARRAY_REMOVE_AT(list, *count, b); changed = true; } } } } }
static void flip_page(struct vo *vo) { struct gl_priv *p = vo->priv; GL *gl = p->gl; mpgl_swap_buffers(p->glctx); p->frames_rendered++; if (p->frames_rendered > 5 && !p->use_gl_debug) gl_video_set_debug(p->renderer, false); if (p->use_glFinish) gl->Finish(); if (p->waitvsync || p->opt_pattern[0]) { if (gl->GetVideoSync) { unsigned int n1 = 0, n2 = 0; gl->GetVideoSync(&n1); if (p->waitvsync) gl->WaitVideoSync(2, (n1 + 1) % 2, &n2); int step = n1 - p->prev_sgi_sync_count; p->prev_sgi_sync_count = n1; MP_DBG(vo, "Flip counts: %u->%u, step=%d\n", n1, n2, step); if (p->opt_pattern[0]) check_pattern(vo, step); } else { MP_WARN(vo, "GLX_SGI_video_sync not available, disabling.\n"); p->waitvsync = 0; p->opt_pattern[0] = 0; } } while (p->opt_vsync_fences > 0 && p->num_vsync_fences >= p->opt_vsync_fences) { gl->ClientWaitSync(p->vsync_fences[0], GL_SYNC_FLUSH_COMMANDS_BIT, 1e9); gl->DeleteSync(p->vsync_fences[0]); MP_TARRAY_REMOVE_AT(p->vsync_fences, p->num_vsync_fences, 0); } }
static struct bstr strip_ext(struct bstr str) { int dotpos = bstrrchr(str, '.'); if (dotpos < 0) return str; return (struct bstr){str.start, dotpos}; } static struct bstr get_ext(struct bstr s) { int dotpos = bstrrchr(s, '.'); if (dotpos < 0) return (struct bstr){NULL, 0}; return bstr_splice(s, dotpos + 1, s.len); } bool mp_might_be_subtitle_file(const char *filename) { return test_ext(get_ext(bstr0(filename))) == STREAM_SUB; } static int compare_sub_filename(const void *a, const void *b) { const struct subfn *s1 = a; const struct subfn *s2 = b; return strcoll(s1->fname, s2->fname); } static int compare_sub_priority(const void *a, const void *b) { const struct subfn *s1 = a; const struct subfn *s2 = b; if (s1->priority > s2->priority) return -1; if (s1->priority < s2->priority) return 1; return strcoll(s1->fname, s2->fname); } static struct bstr guess_lang_from_filename(struct bstr name) { if (name.len < 2) return (struct bstr){NULL, 0}; int n = 0; int i = name.len - 1; if (name.start[i] == ')' || name.start[i] == ']') i--; while (i >= 0 && mp_isalpha(name.start[i])) { n++; if (n > 3) return (struct bstr){NULL, 0}; i--; } if (n < 2) return (struct bstr){NULL, 0}; return (struct bstr){name.start + i + 1, n}; } static void append_dir_subtitles(struct mpv_global *global, struct subfn **slist, int *nsub, struct bstr path, const char *fname, int limit_fuzziness) { void *tmpmem = talloc_new(NULL); struct MPOpts *opts = global->opts; struct mp_log *log = mp_log_new(tmpmem, global->log, "find_files"); if (mp_is_url(bstr0(fname))) goto out; struct bstr f_fname = bstr0(mp_basename(fname)); struct bstr f_fname_noext = bstrdup(tmpmem, strip_ext(f_fname)); bstr_lower(f_fname_noext); struct bstr f_fname_trim = bstr_strip(f_fname_noext); // 0 = nothing // 1 = any subtitle file // 2 = any sub file containing movie name // 3 = sub file containing movie name and the lang extension char *path0 = bstrdup0(tmpmem, path); DIR *d = opendir(path0); if (!d) goto out; mp_verbose(log, "Loading external files in %.*s\n", BSTR_P(path)); struct dirent *de; while ((de = readdir(d))) { struct bstr dename = bstr0(de->d_name); void *tmpmem2 = talloc_new(tmpmem); // retrieve various parts of the filename struct bstr tmp_fname_noext = bstrdup(tmpmem2, strip_ext(dename)); bstr_lower(tmp_fname_noext); struct bstr tmp_fname_ext = get_ext(dename); struct bstr tmp_fname_trim = bstr_strip(tmp_fname_noext); // check what it is (most likely) int type = test_ext(tmp_fname_ext); char **langs = NULL; int fuzz = -1; switch (type) { case STREAM_SUB: langs = opts->sub_lang; fuzz = opts->sub_auto; break; case STREAM_AUDIO: langs = opts->audio_lang; fuzz = opts->audiofile_auto; break; } if (fuzz < 0) goto next_sub; // we have a (likely) subtitle file int prio = 0; char *found_lang = NULL; if (langs) { if (bstr_startswith(tmp_fname_trim, f_fname_trim)) { struct bstr lang = guess_lang_from_filename(tmp_fname_trim); if (lang.len) { for (int n = 0; langs[n]; n++) { if (bstr_startswith0(lang, langs[n])) { prio = 4; // matches the movie name + lang extension found_lang = langs[n]; break; } } } } } if (!prio && bstrcmp(tmp_fname_trim, f_fname_trim) == 0) prio = 3; // matches the movie name if (!prio && bstr_find(tmp_fname_trim, f_fname_trim) >= 0 && fuzz >= 1) prio = 2; // contains the movie name if (!prio) { // doesn't contain the movie name // don't try in the mplayer subtitle directory if (!limit_fuzziness && fuzz >= 2) { prio = 1; } } mp_dbg(log, "Potential external file: \"%s\" Priority: %d\n", de->d_name, prio); if (prio) { prio += prio; char *subpath = mp_path_join(*slist, path, dename); if (mp_path_exists(subpath)) { MP_GROW_ARRAY(*slist, *nsub); struct subfn *sub = *slist + (*nsub)++; // annoying and redundant if (strncmp(subpath, "./", 2) == 0) subpath += 2; sub->type = type; sub->priority = prio; sub->fname = subpath; sub->lang = found_lang; } else talloc_free(subpath); } next_sub: talloc_free(tmpmem2); } closedir(d); out: talloc_free(tmpmem); } static bool case_endswith(const char *s, const char *end) { size_t len = strlen(s); size_t elen = strlen(end); return len >= elen && strcasecmp(s + len - elen, end) == 0; } // Drop .sub file if .idx file exists. // Assumes slist is sorted by compare_sub_filename. static void filter_subidx(struct subfn **slist, int *nsub) { const char *prev = NULL; for (int n = 0; n < *nsub; n++) { const char *fname = (*slist)[n].fname; if (case_endswith(fname, ".idx")) { prev = fname; } else if (case_endswith(fname, ".sub")) { if (prev && strncmp(prev, fname, strlen(fname) - 4) == 0) (*slist)[n].priority = -1; } } for (int n = *nsub - 1; n >= 0; n--) { if ((*slist)[n].priority < 0) MP_TARRAY_REMOVE_AT(*slist, *nsub, n); } } // Return a list of subtitles and audio files found, sorted by priority. // Last element is terminated with a fname==NULL entry. struct subfn *find_external_files(struct mpv_global *global, const char *fname) { struct MPOpts *opts = global->opts; struct subfn *slist = talloc_array_ptrtype(NULL, slist, 1); int n = 0; // Load subtitles from current media directory append_dir_subtitles(global, &slist, &n, mp_dirname(fname), fname, 0); if (opts->sub_auto >= 0) { // Load subtitles in dirs specified by sub-paths option if (opts->sub_paths) { for (int i = 0; opts->sub_paths[i]; i++) { char *path = mp_path_join(slist, mp_dirname(fname), bstr0(opts->sub_paths[i])); append_dir_subtitles(global, &slist, &n, bstr0(path), fname, 0); } } // Load subtitles in ~/.mpv/sub limiting sub fuzziness char *mp_subdir = mp_find_config_file(NULL, global, "sub/"); if (mp_subdir) append_dir_subtitles(global, &slist, &n, bstr0(mp_subdir), fname, 1); talloc_free(mp_subdir); } // Sort by name for filter_subidx() qsort(slist, n, sizeof(*slist), compare_sub_filename); filter_subidx(&slist, &n); // Sort subs by priority and append them qsort(slist, n, sizeof(*slist), compare_sub_priority); struct subfn z = {0}; MP_TARRAY_APPEND(NULL, slist, n, z); return slist; }
// Initialize sub from sub->avsub. static void read_sub_bitmaps(struct sd *sd, struct sub *sub) { struct MPOpts *opts = sd->opts; struct sd_lavc_priv *priv = sd->priv; AVSubtitle *avsub = &sub->avsub; MP_TARRAY_GROW(priv, sub->inbitmaps, avsub->num_rects); packer_set_size(priv->packer, avsub->num_rects); // If we blur, we want a transparent region around the bitmap data to // avoid "cut off" artifacts on the borders. bool apply_blur = opts->sub_gauss != 0.0f; int extend = apply_blur ? 5 : 0; // Assume consumers may use bilinear scaling on it (2x2 filter) int padding = 1 + extend; priv->packer->padding = padding; // For the sake of libswscale, which in some cases takes sub-rects as // source images, and wants 16 byte start pointer and stride alignment. int align = 4; for (int i = 0; i < avsub->num_rects; i++) { struct AVSubtitleRect *r = avsub->rects[i]; struct sub_bitmap *b = &sub->inbitmaps[sub->count]; if (r->type != SUBTITLE_BITMAP) { MP_ERR(sd, "unsupported subtitle type from libavcodec\n"); continue; } if (!(r->flags & AV_SUBTITLE_FLAG_FORCED) && opts->forced_subs_only) continue; if (r->w <= 0 || r->h <= 0) continue; b->bitmap = r; // save for later (dumb hack to avoid more complexity) priv->packer->in[sub->count] = (struct pos){r->w + (align - 1), r->h}; sub->count++; } priv->packer->count = sub->count; if (packer_pack(priv->packer) < 0) { MP_ERR(sd, "Unable to pack subtitle bitmaps.\n"); sub->count = 0; } if (!sub->count) return; struct pos bb[2]; packer_get_bb(priv->packer, bb); sub->bound_w = bb[1].x; sub->bound_h = bb[1].y; if (!sub->data || sub->data->w < sub->bound_w || sub->data->h < sub->bound_h) { talloc_free(sub->data); sub->data = mp_image_alloc(IMGFMT_BGRA, priv->packer->w, priv->packer->h); if (!sub->data) { sub->count = 0; return; } talloc_steal(priv, sub->data); } for (int i = 0; i < sub->count; i++) { struct sub_bitmap *b = &sub->inbitmaps[i]; struct pos pos = priv->packer->result[i]; struct AVSubtitleRect *r = b->bitmap; #if HAVE_AV_SUBTITLE_NOPICT uint8_t **data = r->data; int *linesize = r->linesize; #else uint8_t **data = r->pict.data; int *linesize = r->pict.linesize; #endif b->w = r->w; b->h = r->h; b->x = r->x; b->y = r->y; // Choose such that the extended start position is aligned. pos.x = MP_ALIGN_UP(pos.x - extend, align) + extend; b->src_x = pos.x; b->src_y = pos.y; b->stride = sub->data->stride[0]; b->bitmap = sub->data->planes[0] + pos.y * b->stride + pos.x * 4; sub->src_w = FFMAX(sub->src_w, b->x + b->w); sub->src_h = FFMAX(sub->src_h, b->y + b->h); assert(r->nb_colors > 0); assert(r->nb_colors <= 256); uint32_t pal[256] = {0}; memcpy(pal, data[1], r->nb_colors * 4); convert_pal(pal, 256, opts->sub_gray); for (int y = -padding; y < b->h + padding; y++) { uint32_t *out = (uint32_t*)((char*)b->bitmap + y * b->stride); int start = 0; for (int x = -padding; x < 0; x++) out[x] = 0; if (y >= 0 && y < b->h) { uint8_t *in = data[0] + y * linesize[0]; for (int x = 0; x < b->w; x++) *out++ = pal[*in++]; start = b->w; } for (int x = start; x < b->w + padding; x++) *out++ = 0; } b->bitmap = (char*)b->bitmap - extend * b->stride - extend * 4; b->src_x -= extend; b->src_y -= extend; b->x -= extend; b->y -= extend; b->w += extend * 2; b->h += extend * 2; if (apply_blur) mp_blur_rgba_sub_bitmap(b, opts->sub_gauss); } } static void decode(struct sd *sd, struct demux_packet *packet) { struct MPOpts *opts = sd->opts; struct sd_lavc_priv *priv = sd->priv; AVCodecContext *ctx = priv->avctx; double pts = packet->pts; double endpts = MP_NOPTS_VALUE; double duration = packet->duration; AVSubtitle sub; AVPacket pkt; // libavformat sets duration==0, even if the duration is unknown. Some files // also have actually subtitle packets with duration explicitly set to 0 // (yes, at least some of such mkv files were muxed by libavformat). // Assume there are no bitmap subs that actually use duration==0 for // hidden subtitle events. if (duration == 0) duration = -1; if (pts == MP_NOPTS_VALUE) MP_WARN(sd, "Subtitle with unknown start time.\n"); mp_set_av_packet(&pkt, packet, &priv->pkt_timebase); int got_sub; int res = avcodec_decode_subtitle2(ctx, &sub, &got_sub, &pkt); if (res < 0 || !got_sub) return; if (sub.pts != AV_NOPTS_VALUE) pts = sub.pts / (double)AV_TIME_BASE; if (pts != MP_NOPTS_VALUE) { if (sub.end_display_time > sub.start_display_time && sub.end_display_time != UINT32_MAX) { duration = (sub.end_display_time - sub.start_display_time) / 1000.0; } pts += sub.start_display_time / 1000.0; if (duration >= 0) endpts = pts + duration; // set end time of previous sub struct sub *prev = &priv->subs[0]; if (prev->valid) { if (prev->endpts == MP_NOPTS_VALUE || prev->endpts > pts) prev->endpts = pts; if (opts->sub_fix_timing && pts - prev->endpts <= SUB_GAP_THRESHOLD) prev->endpts = pts; for (int n = 0; n < priv->num_seekpoints; n++) { if (priv->seekpoints[n].pts == prev->pts) { priv->seekpoints[n].endpts = prev->endpts; break; } } } // This subtitle packet only signals the end of subtitle display. if (!sub.num_rects) { avsubtitle_free(&sub); return; } } alloc_sub(priv); struct sub *current = &priv->subs[0]; current->valid = true; current->pts = pts; current->endpts = endpts; current->avsub = sub; read_sub_bitmaps(sd, current); if (pts != MP_NOPTS_VALUE) { for (int n = 0; n < priv->num_seekpoints; n++) { if (priv->seekpoints[n].pts == pts) goto skip; } // Set arbitrary limit as safe-guard against insane files. if (priv->num_seekpoints >= 10000) MP_TARRAY_REMOVE_AT(priv->seekpoints, priv->num_seekpoints, 0); MP_TARRAY_APPEND(priv, priv->seekpoints, priv->num_seekpoints, (struct seekpoint){.pts = pts, .endpts = endpts}); skip: ; } }
static void decode(struct sd *sd, struct demux_packet *packet) { struct MPOpts *opts = sd->opts; struct sd_lavc_priv *priv = sd->priv; AVCodecContext *ctx = priv->avctx; double pts = packet->pts; double endpts = MP_NOPTS_VALUE; double duration = packet->duration; AVSubtitle sub; AVPacket pkt; // libavformat sets duration==0, even if the duration is unknown. Some files // also have actually subtitle packets with duration explicitly set to 0 // (yes, at least some of such mkv files were muxed by libavformat). // Assume there are no bitmap subs that actually use duration==0 for // hidden subtitle events. if (duration == 0) duration = -1; if (pts == MP_NOPTS_VALUE) MP_WARN(sd, "Subtitle with unknown start time.\n"); mp_set_av_packet(&pkt, packet, &priv->pkt_timebase); int got_sub; int res = avcodec_decode_subtitle2(ctx, &sub, &got_sub, &pkt); if (res < 0 || !got_sub) return; if (sub.pts != AV_NOPTS_VALUE) pts = sub.pts / (double)AV_TIME_BASE; if (pts != MP_NOPTS_VALUE) { if (sub.end_display_time > sub.start_display_time && sub.end_display_time != UINT32_MAX) { duration = (sub.end_display_time - sub.start_display_time) / 1000.0; } pts += sub.start_display_time / 1000.0; if (duration >= 0) endpts = pts + duration; // set end time of previous sub struct sub *prev = &priv->subs[0]; if (prev->valid) { if (prev->endpts == MP_NOPTS_VALUE || prev->endpts > pts) prev->endpts = pts; if (opts->sub_fix_timing && pts - prev->endpts <= SUB_GAP_THRESHOLD) prev->endpts = pts; for (int n = 0; n < priv->num_seekpoints; n++) { if (priv->seekpoints[n].pts == prev->pts) { priv->seekpoints[n].endpts = prev->endpts; break; } } } // This subtitle packet only signals the end of subtitle display. if (!sub.num_rects) { avsubtitle_free(&sub); return; } } alloc_sub(priv); struct sub *current = &priv->subs[0]; current->valid = true; current->pts = pts; current->endpts = endpts; current->avsub = sub; MP_TARRAY_GROW(priv, current->inbitmaps, sub.num_rects); MP_TARRAY_GROW(priv, current->imgs, sub.num_rects); for (int i = 0; i < sub.num_rects; i++) { struct AVSubtitleRect *r = sub.rects[i]; struct sub_bitmap *b = ¤t->inbitmaps[current->count]; struct osd_bmp_indexed *img = ¤t->imgs[current->count]; if (r->type != SUBTITLE_BITMAP) { MP_ERR(sd, "unsupported subtitle type from libavcodec\n"); continue; } if (!(r->flags & AV_SUBTITLE_FLAG_FORCED) && opts->forced_subs_only) continue; if (r->w <= 0 || r->h <= 0) continue; #if HAVE_AV_SUBTITLE_NOPICT uint8_t **data = r->data; int *linesize = r->linesize; #else uint8_t **data = r->pict.data; int *linesize = r->pict.linesize; #endif img->bitmap = data[0]; assert(r->nb_colors > 0); assert(r->nb_colors * 4 <= sizeof(img->palette)); memcpy(img->palette, data[1], r->nb_colors * 4); b->bitmap = img; b->stride = linesize[0]; b->w = r->w; b->h = r->h; b->x = r->x; b->y = r->y; current->count++; } if (pts != MP_NOPTS_VALUE) { for (int n = 0; n < priv->num_seekpoints; n++) { if (priv->seekpoints[n].pts == pts) goto skip; } // Set arbitrary limit as safe-guard against insane files. if (priv->num_seekpoints >= 10000) MP_TARRAY_REMOVE_AT(priv->seekpoints, priv->num_seekpoints, 0); MP_TARRAY_APPEND(priv, priv->seekpoints, priv->num_seekpoints, (struct seekpoint){.pts = pts, .endpts = endpts}); skip: ; }
static struct bstr strip_ext(struct bstr str) { int dotpos = bstrrchr(str, '.'); if (dotpos < 0) return str; return (struct bstr){str.start, dotpos}; } static struct bstr get_ext(struct bstr s) { int dotpos = bstrrchr(s, '.'); if (dotpos < 0) return (struct bstr){NULL, 0}; return bstr_splice(s, dotpos + 1, s.len); } static int compare_sub_filename(const void *a, const void *b) { const struct subfn *s1 = a; const struct subfn *s2 = b; return strcoll(s1->fname, s2->fname); } static int compare_sub_priority(const void *a, const void *b) { const struct subfn *s1 = a; const struct subfn *s2 = b; if (s1->priority > s2->priority) return -1; if (s1->priority < s2->priority) return 1; return strcoll(s1->fname, s2->fname); } static struct bstr guess_lang_from_filename(struct bstr name) { if (name.len < 2) return (struct bstr){NULL, 0}; int n = 0; int i = name.len - 1; if (name.start[i] == ')' || name.start[i] == ']') i--; while (i >= 0 && isalpha(name.start[i])) { n++; if (n > 3) return (struct bstr){NULL, 0}; i--; } if (n < 2) return (struct bstr){NULL, 0}; return (struct bstr){name.start + i + 1, n}; } /** * @brief Append all the subtitles in the given path matching fname * @param opts MPlayer options * @param slist pointer to the subtitles list tallocated * @param nsub pointer to the number of subtitles * @param path Look for subtitles in this directory * @param fname Subtitle filename (pattern) * @param limit_fuzziness Ignore flag when sub_fuziness == 2 */ static void append_dir_subtitles(struct MPOpts *opts, struct subfn **slist, int *nsub, struct bstr path, const char *fname, int limit_fuzziness) { void *tmpmem = talloc_new(NULL); if (mp_is_url(bstr0(fname))) goto out; struct bstr f_fname = bstr0(mp_basename(fname)); struct bstr f_fname_noext = bstrdup(tmpmem, strip_ext(f_fname)); bstr_lower(f_fname_noext); struct bstr f_fname_trim = bstr_strip(f_fname_noext); // 0 = nothing // 1 = any subtitle file // 2 = any sub file containing movie name // 3 = sub file containing movie name and the lang extension char *path0 = bstrdup0(tmpmem, path); DIR *d = opendir(path0); if (!d) goto out; mp_msg(MSGT_SUBREADER, MSGL_V, "Load subtitles in %.*s\n", BSTR_P(path)); struct dirent *de; while ((de = readdir(d))) { struct bstr dename = bstr0(de->d_name); void *tmpmem2 = talloc_new(tmpmem); // retrieve various parts of the filename struct bstr tmp_fname_noext = bstrdup(tmpmem2, strip_ext(dename)); bstr_lower(tmp_fname_noext); struct bstr tmp_fname_ext = get_ext(dename); struct bstr tmp_fname_trim = bstr_strip(tmp_fname_noext); // does it end with a subtitle extension? if (!is_sub_ext(tmp_fname_ext)) goto next_sub; // we have a (likely) subtitle file int prio = 0; char *found_lang = NULL; if (opts->sub_lang) { if (bstr_startswith(tmp_fname_trim, f_fname_trim)) { struct bstr lang = guess_lang_from_filename(tmp_fname_trim); if (lang.len) { for (int n = 0; opts->sub_lang[n]; n++) { if (bstr_startswith0(lang, opts->sub_lang[n])) { prio = 4; // matches the movie name + lang extension found_lang = opts->sub_lang[n]; break; } } } } } if (!prio && bstrcmp(tmp_fname_trim, f_fname_trim) == 0) prio = 3; // matches the movie name if (!prio && bstr_find(tmp_fname_trim, f_fname_trim) >= 0 && opts->sub_match_fuzziness >= 1) prio = 2; // contains the movie name if (!prio) { // doesn't contain the movie name // don't try in the mplayer subtitle directory if (!limit_fuzziness && opts->sub_match_fuzziness >= 2) { prio = 1; } } mp_msg(MSGT_SUBREADER, MSGL_DBG2, "Potential sub file: " "\"%s\" Priority: %d\n", de->d_name, prio); if (prio) { prio += prio; char *subpath = mp_path_join(*slist, path, dename); if (mp_path_exists(subpath)) { MP_GROW_ARRAY(*slist, *nsub); struct subfn *sub = *slist + (*nsub)++; // annoying and redundant if (strncmp(subpath, "./", 2) == 0) subpath += 2; sub->priority = prio; sub->fname = subpath; sub->lang = found_lang; } else talloc_free(subpath); } next_sub: talloc_free(tmpmem2); } closedir(d); out: talloc_free(tmpmem); } static bool case_endswith(const char *s, const char *end) { size_t len = strlen(s); size_t elen = strlen(end); return len >= elen && strcasecmp(s + len - elen, end) == 0; } // Drop .sub file if .idx file exists. // Assumes slist is sorted by compare_sub_filename. static void filter_subidx(struct subfn **slist, int *nsub) { const char *prev = NULL; for (int n = 0; n < *nsub; n++) { const char *fname = (*slist)[n].fname; if (case_endswith(fname, ".idx")) { prev = fname; } else if (case_endswith(fname, ".sub")) { if (prev && strncmp(prev, fname, strlen(fname) - 4) == 0) (*slist)[n].priority = -1; } } for (int n = *nsub - 1; n >= 0; n--) { if ((*slist)[n].priority < 0) MP_TARRAY_REMOVE_AT(*slist, *nsub, n); } } // Return a list of subtitles found, sorted by priority. // Last element is terminated with a fname==NULL entry. struct subfn *find_text_subtitles(struct MPOpts *opts, const char *fname) { struct subfn *slist = talloc_array_ptrtype(NULL, slist, 1); int n = 0; // Load subtitles from current media directory append_dir_subtitles(opts, &slist, &n, mp_dirname(fname), fname, 0); // Load subtitles in dirs specified by sub-paths option if (opts->sub_paths) { for (int i = 0; opts->sub_paths[i]; i++) { char *path = mp_path_join(slist, mp_dirname(fname), bstr0(opts->sub_paths[i])); append_dir_subtitles(opts, &slist, &n, bstr0(path), fname, 0); } } // Load subtitles in ~/.mpv/sub limiting sub fuzziness char *mp_subdir = mp_find_user_config_file("sub/"); if (mp_subdir) append_dir_subtitles(opts, &slist, &n, bstr0(mp_subdir), fname, 1); talloc_free(mp_subdir); // Sort by name for filter_subidx() qsort(slist, n, sizeof(*slist), compare_sub_filename); filter_subidx(&slist, &n); // Sort subs by priority and append them qsort(slist, n, sizeof(*slist), compare_sub_priority); struct subfn z = {0}; MP_TARRAY_APPEND(NULL, slist, n, z); return slist; }