:- module(yaz_tag_agreement, [ ]). :- use_module(library(http/http_dispatch)). :- use_module(library(http/http_parameters)). :- use_module(library(http/http_path)). :- use_module(library(http/html_write)). :- use_module(library(http/html_head)). :- use_module(library(http/http_json)). :- use_module(library(http/js_write)). :- use_module(library(http/json)). :- use_module(library(http/json_convert)). :- use_module(library(http/http_session)). :- use_module(user(user_db)). :- use_module(library(semweb/rdf_db)). :- use_module(library(semweb/rdfs)). :- use_module(library(semweb/rdf_label)). :- use_module(library(yaz_util)). :- use_module(library(yui3)). :- use_module(library(user_process)). :- use_module(library(video_annotation)). :- use_module(library(tag_match)). %:- use_module(library(reconcile)). :- use_module(components(label)). :- use_module(components(yaz_page)). :- use_module(api(video_frames)). :- setting(reconcile_server, atom, 'http://standard-reconcile.freebaseapps.com/reconcile', 'URL of a reconcile server, use "local" for built-in service of this server'). :- http_handler(yaz(tagagreement), http_yaz_tag_agreement, []). :- http_handler(yaz('data/frames'), http_data_frames, []). http_yaz_tag_agreement(Request) :- ensure_logged_on(User0), user_property(User0, url(User)), http_parameters(Request, [ video(Video, [description('Current video')]), limit(_Limit, [default(1000), number, description('limit number of tags shown')]) ]), create_user_process(User, [rdf:type=pprime:'TagGarden', opmv:used=Video ], _Process), findall(Tag, video_tag(Video, literal(Tag)), Tags0), sort(Tags0, Tags), concept_candidates(Tags, Pairs), tag_clusters(Pairs, TagClusters), maplist(cluster_annotation(Video), TagClusters, Annotations0), sort_by_arg(Annotations0, 3, Annotations1), reverse(Annotations1, Annotations), html_garden_page(Video, Annotations, []). cluster_annotation(Video, Tags, annotation(Label, Ts, Count)) :- findall(literal(Tag), (member(Tag-_, Tags), video_tag(Video, literal(Tag)) ), Ts0), sort(Ts0, Ts), % get most occuring tag Ts0 = [Label|_], length(Ts0, Count). concept_candidates([], []). concept_candidates([Tag|Ts], [Tag-Cs|Rest]) :- findall(C, find_tag_concept(Tag, C, _), Cs), concept_candidates(Ts, Rest). tag_clusters([], []). tag_clusters([Tag|T0], [[Tag|Tags]|T]) :- similar_tags(T0, Tag, Tags0, Rest0), extend_cluster(Tags0, Rest0, Tags, Rest), tag_clusters(Rest, T). similar_tags([], _, [], []). similar_tags([Tag|T], Tag0, [Tag|Tags], Rest) :- my_tag_match(Tag, Tag0), !, similar_tags(T, Tag0, Tags, Rest). similar_tags([Tag|T], Tag0, Tags, [Tag|Rest]) :- similar_tags(T, Tag0, Tags, Rest). extend_cluster([], Rest, [], Rest). extend_cluster([Tag|Ts], Tags, Extend, Rest) :- append(Extend0, [Tag|Extend1], Extend), similar_tags(Tags, Tag, Extend0, Rest0), extend_cluster(Ts, Rest0, Extend1, Rest). my_tag_match(T1-_, T2-_) :- snowball(english, T1, T1S0), snowball(english, T2, T2S0), downcase_atom(T1S0, T1S), downcase_atom(T2S0, T1S). my_tag_match(_T1-Cs1, _T2-Cs2) :- member(C1, Cs1), member(C2, Cs2), tag_concept_match(C1, C2, _). tag_concept_match(R, R, synonym) :- !. tag_concept_match(R1, R2, specific) :- parent_of(R1, R2), !. tag_concept_match(R1, R2, generic) :- parent_of(R2, R1), !. %% parent_of(+R, +Ancestor) % % True if Parent is related to R by skos:broader. parent_of(R, A) :- rdf_has(R, skos:broader, A). parent_of(R, A) :- rdf_has(A, skos:broader, R). %% http_data_frames(+Request) % % Emit a JSON object with all frames for a given tag and video. http_data_frames(Request) :- http_parameters(Request, [ video(Video, [description('Current video')]), process(Process, [optional(true), desription('When set only annotations within this process are shown')]), user(User, [optional(true), description('When set only annotations created by this user are shown')]), limit(_Limit, [default(1000), number, description('limit number of tags shown')]), tag(Tags, [jsonresource, description('Tag assinged to the video')]) ]), Options = [process(Process), user(User) ], Obj = Time-json([tag=Label, entry=Id, time=Time]), findall(Obj, (member(T,Tags), tag_term_label(T,_,Label), video_annotation(Video, Id, T, Time, Options)), TagEntries0), keysort(TagEntries0, TagEntries), pairs_values(TagEntries, Fragments), reply_json(json([fragments=Fragments])). /******************************* * HTML * *******************************/ %% html_garden_page(+Video, +Annotations, +Options) % % Emit an HTML page for tag gardening. html_garden_page(Video, Annotations, Options) :- reply_html_page(yaz, [ title(['YAZ - ', Video]) ], [ \html_requires(css('garden.css')), div(class('yui3-skin-sam'), \html_page_layout(Video)), script(type('text/javascript'), \html_page_yui(Video, Annotations, Options)) ]). html_page_layout(Video) --> { display_label(Video, Title) }, html([ h2(Title), div(id(tags), [ %h4('Tags'), div(id(taglist), []) ]), div(id(main), [ div([id(concepts), class(hidden)], [ div(id(tagReconcile), []) ]), %h4('Fragments'), div(id(frames), []) ]), div([id(suggest), class(hidden)], [ input([type(text), id(taginput)]), div(id(suggestResults), []) ]) ]). /******************************* * JS * *******************************/ %% html_page_yui(+Video, +Annotations, +Options) % % Emit JavaScript for the tag gardening page. html_page_yui(Video, Annotations, _Options) --> { video_source(Video, Src, Duration), http_absolute_location(js('videoplayer/'), PlayerPath, []), http_absolute_location(js('videoplayer/videoplayer.js'), VideoPlayer, []), http_absolute_location(js('tagcarousel/tagcarousel.js'), TagCarousel, []), http_absolute_location(js('videoframes/videoframes.js'), VideoFrames, []), http_absolute_location(js('timeline/timeline.js'), Timeline, []), http_location_by_id(serve_video_frame, FrameServer), http_location_by_id(http_data_frames, DataServer), ( setting(reconcile_server, local) -> http_location_by_id(http_reconcile, ReconcileServer) ; setting(reconcile_server, ReconcileServer) ), annotation_to_json(Annotations, JSONTags) }, html_requires(js('videoplayer/swfobject.js')), html_requires(yui3('yui/yui-min.js')), js_yui3([{modules:{'video-player':{fullpath:VideoPlayer}, 'tag-carousel':{fullpath:TagCarousel}, 'video-frames':{fullpath:VideoFrames}, 'timeline':{fullpath:Timeline} }} ], [node,event,widget,anim,slider,'align-plugin', 'querystring-stringify-simple','io','jsonp','json', 'video-player','tag-carousel','timeline', 'video-frames' ], [\js_new(tagList, 'Y.mazzle.TagCarousel'({topIndent:symbol(false), tags:JSONTags, height:500, width:200, edit:true, suggest:symbol('Y.one("#suggest")') })), \js_new(videoFrames, 'Y.mazzle.VideoFrames'({frameServer:FrameServer, dataServer:DataServer, video:Src, duration:Duration, playerPath:PlayerPath })), 'Y.one("#suggest").plug(Y.Plugin.Align);\n', \js_call('tagList.render'('#taglist')), \js_call('videoFrames.render'('#frames')), \js_yui3_on(tagList, itemSelect, \js_tag_select(Video)), \js_yui3_on(videoFrames, confirmSelect, \js_confirm_select), %\js_call('videoFrames.fetchData'), \js_freebase_suggest ]). js_tag_select(Video) --> js_function([e], [ ' var tag = Y.JSON.stringify(e.tag.tags); videoFrames.set("confirm", true); videoFrames.fetchData({video:"',Video,'", tag:tag});\n' ]). js_concept_select --> js_function([e], [ 'videoFrames.set("concept", e.concept);\n' ]). js_confirm_select --> { http_location_by_id(http_confirm_fragment, Path) }, js_function([e], [ 'var f = e.frame; var tag = e.concept ? e.concept.name : f.tag; var uri = e.concept ? e.concept.id : null;\n', \js_call('Y.io'(Path, {data: {type:symbol('e.type'), video:symbol('f.video'), starttime:symbol('f.time'), tag:symbol(tag), uri:symbol(uri)} })) ]). js_freebase_suggest --> html_requires('http://freebaselibs.com/static/suggest/1.2.1/suggest.min.css'), html_requires(js('jquery/jquery-1.4.2.min.js')), html_requires('http://freebaselibs.com/static/suggest/1.2.1/suggest.min.js'), html(\[ '$("#taginput").suggest({parent:"#suggestResults", soft:true, required:true, flyout:true})\n' ]). :- http_handler(yaz(confirm), http_confirm_fragment, []). %% http_confirm_fragment(+Request) % % Handler for confirmation of video fragment http_confirm_fragment(Request) :- http_in_session(SessionID), logged_on(User, SessionID), http_parameters(Request, [ video(Video, [description('URL of video')]), starttime(StartTime, [number, description('StartTime of fragment')]), endtime(EndTime, [number, optional(true), description('EndTime of fragment')]), type(RoleType, [optional(true), description('Role of the tag')]), tag(Tag, [description('Tag being confirmed')]), uri(URI, [optional(true), description('URI of confirmed concept')]) ]), ( var(EndTime0) -> EndTime is StartTime + 10000 ; EndTime = EndTime0 ), rdf_transaction(assert_confirm_event(Video, StartTime, EndTime, Tag, URI, RoleType, User)), reply_json(json([success='confirmation saved', user=User, video=Video, starttime=StartTime, endtime=EndTime, tag=Tag, uri=URI ])). assert_confirm_event(Video, StartTime, EndTime, Tag, URI, RoleType, User) :- rdf_bnode(CE), rdf_assert(CE, rdf:type, pprime:'ConfirmEvent', User), rdf_assert(CE, sem:involves, Video, User), rdf_assert(CE, sem:hasActor, User, User), rdf_bnode(FA), rdf_assert(FA, rdf:type, pprime:'FragmentAnnotation', User), rdf_assert(FA, sem:subEventOf, CE, User), rdf_assert(FA, sem:hasBeginTimeStamp, literal(StartTime), User), rdf_assert(FA, sem:hasEndTimeStamp, literal(EndTime), User), rdf_assert(FA, sem:involves, literal(Tag), User), ( var(URI); URI==null -> URI = null ; rdf_assert(FA, sem:involves, URI, User) ), ( var(Type) -> Type = null ; rdf_assert(FA, pprime:role, RoleType, User) ).